Dziękuję wszystkim za rady.
Mam tylko pytanie jeszcze co do pkt 1.
Wszystkie testy powinny być w osobnych klasach i według mnie właśnie to pozwala ładnie zapanować nad kodem. Niestety - jeśli mamy n klas to w rezultacie wyjdzie nam 2n klas wraz z testami.
Czy nie spowoduje to rozdęcia kodu?
TDD oczywiście powoduje, że napiszesz znacznie więcej kodu, niż gdybyś olewał testowanie. Ale z drugiej strony:
- debugowanie w złożonym projekcie jest znacznie bardziej uporczywe i czasochłonne niż otestowanie kodu i wyczajenie błędu w dobrze otestowanym kodzie,
- dobrze napisane testy jednostkowe, najlepiej w konwencji BDD http://dannorth.net/introducing-bdd/ http://jbehave.org/ są swoistą wykonywalną dokumentacją, wykonywalnymi przypadkami użycia, które są przez cały czas w 100 % zsynchronizowane z kodem - wyobraź sobie nieustanną synchronizację dużej ilości dokumentacji zewnętrznej z kodem, jest to koszmar, do którego bardzo łatwo się zniechęcić,
- TDD w zasadzie wymusza loose-coupling, modułową budowę programu. To też powoduje,że kodu jest więcej, ale z drugiej strony podmiana lub dodanie jednego alternatywnego klocka (modułu) nie jest wielkim problemem.
- ogólnie TDD jest do projektów, które ewoluują, a prawie każdy projekt dość mocno ewoluuje. No chyba, że 1) projekt jest malutki, a technologia znana na wylot albo 2) project manager ma szklaną kulę i jest w stanie przewidzieć każdy szczegół na 10 lat wprzód,
1.) Czy kod/y test/ów powinien znajdować się w klasie, którą testuje? Czy może stworzyć osobną klasę np "Testklient"?
Czy może jeszcze inaczej - stworzyć osobną klasę ogólną do testowania np "testujWszystko" i tam kody testów każdej klasy w odpowiednich funkcjach?
Ja stosuję konwencję, która jest stosowana przez Mavena czy NetBeansa: testy umieszczam w osobnym folderze kodu z identyczną strukturą paczek jak w kodzie testowanym. Klasy testujące umieszczam w tych samych pakietach, co klasy testowane (oczywiście jest to osobny katalog jak wynika z poprzedniego zdania). Polecam zajrzeć do dokumentacji Mavena i zapoznać się z jego konwencjami.
Oczywiście test jednostkowy to test jednostkowy, a jednostką jest klasa, więc trzeba zrobić osobny test dla każdej klasy. No i rozdzielić kod testowany od testującego tak jak napisałem przed chwilą.
Kod testu polega na tym, aby ustalić czy dany fragment działa prawidłowo. W związku z tym należy po prostu testować dany fragment na wszystkie możliwe sposoby? Wywoływać funkcje z różnymi parametrami itd?
Kod jest w 100 % przetestowany jeżeli:
a) jest udowodniony analogicznie do dowodów matematycznych,
b) działa dobrze dla każdych możliwych danych na wejściu,
Z tego wynika, iż jeśli np tworzymy funkcję NWD(a, b) dla 32-bitowych intów, to musimy wybrać jedną z dwóch opcji:
a) rozpisać formalny dowód poprawności programu, ale to prawie zawsze jest zbyt kosztowne (w jakimkolwiek sensie),
b) przetestować go dla wszystkich danych wejściowych, czyli przetestować dla 2(32 + 32) = 264 kombinacji parametrów wejściowych. Wtedy będziemy mieli pewność, że dla każdych argumentów działa dobrze. Jednak takie przetestowanie jest niemożliwe ze względów czasowych,
Dlatego zwykle nie testuje się kod w 100 % ale stosuje się różne kryteria, np pokrycie kodu testami, do czego może być przydatne np takie narzędzie jak Cobertura: http://cobertura.sourceforge.net/ Z tym, że nawet jeśli nasz kod pokrywa 100 % kodu testowanego z każdą możliwą kombinacją rozgałęzień w kodzie to i tak w ogólności nie możemy być 100 % pewni, że kod testowany jest poprawny. Jak napisałem wyżej, zwykle musielibyśmy odpalić tak dużą ilość testów, że przetestowanie stałoby się niemożliwe przed kolejnym Wielkim Wybuchem. Ale jeśli kod jest przetestowany wystarczająco profesjonalnie to szansa, że jakieś błędy się jeszcze uchowały jest bardzo mała.
Kod testów powinien zostawać po zakończeniu testu, ponieważ ułatwia to testowanie w następnych etapach projektu. Czy taka sama zasada obowiązuje, gdy projekt jest już ukończony? Czy wtedy nadal kody testów powinny zostać w programie?
Jeśli masz dobrze skonfigurowany projekt (o ile twoje IDE na to pozwala) to przy budowaniu kodu do jego wydania (release) kod testujący w ogóle nie powinien zostać wkompilowany. Dla przykładu w Mavenie jest konwencja (jak napisałem powyżej), że są np takie dwa katalogi jak main/ z kodem produkcyjnym i test/ z kodem testującym. Podczas testowania są wkompilowywane kody z obu folderów, a przy budowaniu produktu końcowego kod testujący nie jest wkompilowywany.
Jest coś takiego jak skończony projekt? Według mnie projekty dzielą się na rozwijane albo porzucone (niewspierane). Jeśli chcesz zgarniać kasę za sprzedaż programu to musisz na bieżąco usuwać błędy, przed którymi nie uchronisz się nawet stosując TDD (ale będzie ich wtedy znacznie mniej), a więc pozbywanie się testów byłoby głupotą. Z drugiej strony, jeśli porzucasz projekt, to porzucasz go w całości.
- jw, są też testy integracyjne (testujące "styki" komponentów)
Nie tylko styki komponentów, ale też interakcję ze środowiskiem. Dla przykładu ja teraz robię aplikacje dla Androida i JavyME i testami integracyjnymi nazywa się tam testy, które wymagają odpalenia emulatora do działania. Emulator dostarcza pewnych API związanych ze sprzętem, a więc nazwa test integracyjny jest tutaj w zasadzie słuszna.
Wyczytałem, że kod testujący najlepiej pisać przed kodem właściwym (produkcyjnym). Czy to prawda?
Tak twierdzą ludzie dobrze obeznani w TDD (ja w sumie dopiero zaczynam, na serio to piszę powolutku testy dopiero gdzieś od początku roku). Moim zdaniem taka dyscyplina jest sensowna wtedy, gdy dobrze znasz wykorzystywane technologie i twoja praca nie jest jednym wielkim eksperymentowaniem z API. Jeśli robisz kolejny projekt w jakiejś tam technologii, to mniej więcej wiesz jak wygląda jej użycie, wobec czego napisanie testów powinno być łatwe.
a ja mam do Was pytanie z innej beczki - sami piszecie testy jednostkowe do swoich klas? Bo jak mi gość od testów odszedł to próbowałem sam napisać (podkreślam do własnych klas) i wyglądało to tak, że kod testu to była praktycznie kopia kodu klasy . I nie wyobrażam sobie jakby to inaczej zrobić bo przecież nagle nie wpadnę na genialny całkiem inny algorytm, który będzie robił to samo co ten już zaimplementowany. Ba nikt mi nie da gwarancji, że w szczególnych przypadkach ten nowy nie będzie liczył źle. Jak Wy się na to zapatrujecie?
Jeśli masz jakąś czystą (w sensie matematycznym, czyli bez efektów ubocznych) funkcję to robisz sobie listę kombinacji parametrów wejściowych i oczekiwanych wyników. A więc jeśli np testujesz funkcję podnieśXDoPotęgiA(int x, int a); to danymi wejściowymi mogą być List((1, 2), (2, 3), (3, 2)), a oczekiwanymi wynikami List(1, 8, 9).
No i jeszcze jedna sprawa dość ważna:
W moim odczuciu, jednostką w testach jednostkowych jest klasa, a nie pojedyncza funkcja. Chodzi o testowanie funkcjonalności klasy, a nie testowanie poszczególnych funkcji z osobna. W sumie to taka jest filozofia BDD. Mamy bardziej sprawdzać czy funkcja ma dobrze zaimplementowane funkcjonalności i (to nie zawsze ma miejsce) czy dobrze oddziałuje z otoczeniem, np czy klasa Bankomat nie wywołuje z klasy Bank funkcji, których nie powinna.
Polecam poczytać o BDD, mi to wiele wyjaśniło ;)