1001
pytań

o testach

Michał Stocki

Young Digital Planet, Styczeń 2017

1. Po co są testy?

  1. szósty zmysł przy projektowaniu
    • zły kod jest ciężko przetestować
      "Listen to your tests!"
    • ułatwiają start nad grubym feature'am
      pisząc najpierw testy, lepiej rozumiemy co mamy do zrobienia
  2. specyfikacja i dokumentacja
    • wysoko poziomowa – nazwy testów
      jakie przypadki rozważamy
    • nisko poziomowa – implementacja testów
      "specification by example"

1. Po co są testy?

  1. pomoc w myśleniu abstrakcyjnym
    zmuszają do oddzielenia tego co chcemy osiągnąć od tego jak chcemy to osiągnać
  2. wyznacznik tego kiedy możemy przestać kodować
    dowód poprawnej, kompletnej implementacji
  3. ochrona przed regresem
    pozwalają niskim kosztem robić bezpiecznie duże zmiany
  4. motywacja przez wizualizację postępu pracy
    oraz uczucie satysfakcji, kiedy w końcu wszystko jest na zielono

2. Czy i kiedy warto stosować TDD?

Warto zawsze z wyjątkiem sytuacji,
kiedy nie bardzo się da:

  • duże ryzyko – nie jesteśmy w stanie przwidzieć czy nasz pomysł na realizację ma szansę powodzenia. Wtedy PoC potem dopiero TTD.
  • implementujemy klasę widokową, a testy e2e nie są pisane przez programistów

Typowe choroby testów pisanych po implementacji:

  • silne powiązanie z implementacją, brak elastyczności (zabetonowane)
  • brak pokrycia części ścieżek
  • złe asercje np. dla getterów
  • opisy zawierające nazwy kodowe zamiast pojęć domenowych

3. Czy każdy projekt powinien być wyposażony w testy automatyczne?

Powinniśmy pisać testy do każdego projektu, który wychodzi z fazy prototypu.

Rozwijanie jakiegokolwiek projektu, który nie jest pokryty testami, wiąże się z dużym ryzykiem, a ryzyko to będzie rosło wraz z wiekiem i rozmiarem projektu.

Nie każdy projekt powinien mieć jednak testy jednostkowe. Dla większości małych projektów wystarczą testy e2e (w różnej postaci).

4. Czy do każdej klasy/funkcji powinno się pisać testy?

I tak i nie. Każda logika powinna być pokryta testami, co nie oznacza, że każda klasa czy funkcja wymaga odrębnego zestawu testów

Przykłady:

  • Czasem cały zestaw strategii (rozdzielony na wiele klas) da się przetestować jednym zestawem testów
  • Logika nastawiona na efekt czysto wizualny, powinna być testowana e2e

5. Czym jest „jednostka” w testach jednostkowych?

Test jednostkowy powinien pokrywać najmniejszą część kodu, która może zostać użyta ponownie, lub mniejszą, jeśli jest to konieczne ze względu na dużą liczbę przypadków testowych

W przeciwnym wypadku ryzykujemy, że logika nie przestanie być testowana w momencie, kiedy usuniemy kontekst użycia, w którym została przetestowana. Przykłady:
  • Warto przetestować jednostkowo nawet pojedynczą funkcję, która robi generyczną operację – może być użyta wielokrotnie
  • Nie warto testować osobno pojedynczej strategii, która nie zawiera rozgałęzień logiki a jej użycie poza mechanizmem strategii nie ma sensu.

6. Czy testować dyrektywy Angularowe?

Tak, ale nie jednostkowo.
Używajmy do tego testów end-to-end

Z mojego doświadczenia wynika, że graniczy z cudem napisanie testu do dyrektywy przed jej implementacją. To z kolei oznacza, że testy będą słabe. Nawet jeśli się napisać test najpierw, będzie to zawsze test ściśle związany z implementacją – ten sam efekt wizualno-funkcjonalny możemy bowiem osiągnąć żonglując HTML-em na dziesiątki sposobów.

Środowisko do testów e2e posia odpowiednie narzędzia do potwierdzania poprawnego działania widoków. Testy jednostkowe się do tego nie nadają. Starajmy się więc całą logikę wydzielić poza dyrektywę i dopiero tam przetestować jednostkowo.

7. Czy warto generować losowo przypadki testowe?

Nie. Zdecydowanie nie powinno się tego robić.

Jedną z podstawowych cech testów jednostkowych powinna być ich deterministyczność.

Test, który wywala się raz na 300 uruchomień nie jest do niczego przydatny.

Pokusą do losowania wartości użytych w teście może być:

  • duża liczba podobnych przypadków testowych – użyjmy wtedy testów parametryzowanych.
  • chęć wykazania, że np. liczba może przyjąć dowolną wartość z danego zakresu – wówczas należy napisać testy dla wartości brzegowych oraz pojedynczej, dowolnie wybranej wartości z zaresu.

8. Jak poprawnie nazywać testy?

Przydatnych może być kilka wskazówek:

  • testy powinny opisywać zachowanie klasy a nie jej metody.
  • piszmy je tak jak byśmy opowiadali koledze z poza zespołu o tym co robi nasza logika – skupiając się na jasnym i precyzyjnym przekazie ale abstrahując od implementacji
  • dobrym zwyczajem jest opisanie na początku choć jednym zdaniem odpowiedzialności klasy, tak żeby czytający znał kontekst

8. Jak poprawnie nazywać testy?


describe('PageTextRangeMerger', () => {
  describe('merging given list of page text ranges into a single page text range', () => {
    describe('when more then one range were given', () => {
      it('returns a new range which begin index equals the earliest begin index within the given ranges' +
        'and end index equals the farthest end index within the given ranges', () => {
        // ...
      });

      it('returns a new range with have page id of the first given range to be merged', () => {
        // ...
      });
    });

    describe('when the only one range was given', () => {
      it('returns a new range being equal the given range', () => {
        // ...
      });
    });
  });
});

					

9. Czy warto używać nazw metod jako nazw testów?

Nie! To jeden z najczęstrzych błędów

nie powinniśmy używać nazw zmiennych ani metod – nie zrefactorują się automatycznie, a poza tym skoro ktoś czyta nazwę testu, oznacza to, że nazwa metody nie była dla niego wystarczająco jasna

10. Czy warto stosować testy parametryzowane?

To zależy.

Testy parametryzowane dobrze sprawdzają się tam gdzie mamy wiele podobnych przypadków a nie mamy rozgałęzień logiki. Warto jednak w testach parametryzowanych nazwać słownie jaki przypadek brzegowy jest ilustrowany przez konkretny zestaw danych testowych.

11. Czy i kiedy warto mockować?

Warto mockować wszędzie tam, gdzie użycie prawdziwej klasy jest kosztowne.

12. Czy warto mockować biblioteki zewnętrzne (zależności)?

Nie. Zdecydowanie nie powinno się tego robić.

Mockując zależności narażamy się na to, że nie wykryjemy regresu spowodowanego przez update zależności. Niskopoziomowe zależności (np. lodash) traktujmy jak nasz własny kod – testujmy tak jak by wcale nie zostały użyte w implementacji. Wysokopoziomowe zależności (np. video.js) abudowujemy adapterem, w celu uniknięcia negatywnych skutków zmiany API.

13. Czy warto mockować API środowiska (np. DOM API)?

Zdecdowanie nie.

14. Czy zawsze trzeba przetestować wszystkie ścieżki? Co jeśli jest ich 3*4*12?

Zawsze trzeba przetestować wszystkie ścieżki, jednak zbyt duża ich liczba w jednym zestawie testów jest wyraźnym sygnałem, że mamy źle wydzielone klasy

15. Czy pisać przypadki testowe dla niepoprawnych danych (np. null zamiast jakiegoś argumentu)?

Nie. Testujemy tylko to, czego się spodziewamy.

16. Czy trzeba zawsze ograniczać się do pojedynczej asercji w każdym teście?

Niekoniecznie. Czasem do potwierdzenia jednego zachowania, potrzebnych jest kilka asercji.

Koniec