Komunikator a la gadu gadu - testy jakie powinien przejść

0

Chcę co nieco poćwiczyć jeśli chodzi o podejście tdd, tak żeby pokryć testami większość kodu komunikatora.
Jakie powinny być przykładowe testy dla takiej aplikacji?
Mi przychodzą takie pomysły:

  1. czy socket został poprawnie utworzony i przypisany do portu
  2. czy połączenie się udało
  3. czy program normalnie zamyka połączenie
  4. działanie programu w przypadku awarii internetu
  5. czy strumień znaków przychodzącyh jest różny on nulla
3

To brzmi jak jakieś mocno wybiórcze unit testy. Zacznij moze od testów akceptacyjnych, przynajmniej na poziomie testów integracyjnych a może i e2e? Czyli takie rzeczy jak:

  • wysłana wiadomość dochodzi do odbiorcy w skończonym czasie
  • jeśli odbiorca nie był aktywny w chwili wysłania wiadomości, to po zalogowaniu się dostaje wiadomość (corner case, ktoś sie zrobił aktywny na chwilę, potem znowu stracił połączenie i musisz mieć pewność że wiadomość została dostarczona albo nie)
  • można przesłać wiadomość dowolnej (ograniczonej?) długości

Takie testy jak opisałeś są za bardzo niskopoziomowe i whiteboxowe, zeby w ogóle mówić o TDD. Testy powinny opisywać i testować zachowanie a nie implementacje. A u ciebie masz socket, połączenie, strumień znaków, nulle. To są wszystko techniczne detale! Możesz mieć komunikator na UDP i wtedy nie ma połączeń żadnych. Możesz opierac to o webserwisy i wtedy nie masz nigdzie gołych socketów itd.

1

Jeśli mogę coś zaproponować, to zainteresuj się property testingiem. W ten sposób będziesz miał ogromną ilość testów, testujących często dziwne przypadki, praktycznie za darmo. Przykładowo:

property "non-empty message sent is received" do
  from = create_user()
  to = create_user()

  check all message <- string(:printable, min_length: 1) do
    Communicator.send(message, from, to)

    assert message in Communicator.messages(to)
  end
end

test "empty message is not sent" do
  from = create_user()
  to = create_user()

  Communicator.send("", from, to)

  assert [] == Communicator.messages(to)
end

(tutaj użyłem przykładu w Elixirze, ale idea mam nadzieję jest dość czytelna)

W ten sposób masz zapewnione, że jeśli ktoś wyśle ciąg drukowalnych znaków, to odbiorca powinien taką wiadomość otrzymać. Dodatkowo jeśli gdzieś będzie błąd to narzędzia tego typu są "inteligentne" na tyle, że postarają się zrobić redukcję przykładów, by zwrócić jak najmniejszy możliwy przykład, który wywołuje błąd.

1
  from = create_user()
  to = create_user()

Osobiście jestem zwolennikiem fluent buildera dla konfiguracji testowej, zamiast takiego dziubania, szczególnie przydatne jak robi się to bardziej złożone. Więc coś w stylu:

// given
testConfiguration = testHelper.createConfiguration()
.withRandomSender()
.withRandomReceiver()
.withX()
.withY()
.withZ()
setup();

// when
result = client.sendMessage(testConfiguration.sender(), testConfiguration.receiver(), "message");

// then
assert...

Potem takie testy lepiej się czyta, bo często nie musisz wiedzieć/pamiętać "jak" się pewne rzeczy dzieją (np. że tworzenie usera pisze coś do bazy, albo mockuje odpwiedź jakiegoś mikroserwisu w odpowiedni sposób itd), tylko niejako deklaratywnie specyfikujesz stan systemu przed testem.

0

@Shalom: normalnie to by wyglądało tak:

# tutaj można też przekazać atom z nazwą funkcji unarnej, np. `:create_users` i wtedy zostanie ona wywołana z kontekstem poprzednich wywołań
setup do
  user_a = create_user()
  user_b = create_user()

  {:ok, user_a: user_a, user_b: user_b}
end

test "…", %{user_a: user_from, user_b: user_to} do
  # …
end

Tutaj dałem to tak jak wyżej by było szybciej, czyściej i mniej "magicznie".

1 użytkowników online, w tym zalogowanych: 0, gości: 1