[UT-6] Jak testować?

3

[ten post jest częścią mojego minicyklu o testach, pełna lista postów: tutaj]

Odpowiedź na pytanie postawione w tytule to temat nie na posta, ale na całą (może nawet niejedną) książkę. Poniżej postaram się nakreślić najważniejsze według mnie aspekty tworzenia testów… chociaż na pewno lista ta nie jest kompletna.

Aha, no i nie jestem w stanie podać niezawodnej recepty na "dobry test". Zgłębiam temat od dobrych kilku lat i sam ciągle się uczę, więc cudów nie ma – praktyka i identyfikowanie własnych pomyłek jest najlepszym nauczycielem:).

Testy to też kod!

Do poprawnej implementacji przydatnych testów konieczne jest uświadomienie sobie, że testy to kod tak samo ważny jak kod produkcyjny (czasami miewam nawet przebłyski przeświadczenia, że ważniejszy, ale nie mam jeszcze tej kwestii na tyle przemyślanej, żeby publicznie ją ujawniać i wdawać się w dyskusje na ten temat). Szczególnie na początku przygody z testami bardzo trudno jest zaakceptować taki tok myślenia. A bo to przecież jakaś osobna dllka, która sobie krąży obok "właściwego" rozwiązania i nigdy nie jest nikomu dostarczana, nie wykonuje się w środowisku produkcyjnym, nie zarabia na siebie… Błąd!

Do testów powinno się podchodzić z taką samą, lub nawet większą, dbałością, jak to każdego innego elementu systemu. A już na pewno nie można ich lekceważyć. Koniec końców prawdopodobnie linii testujących będziemy mieli o wiele więcej niż linii testowanych… więc czy cokolwiek daje nam prawo dmuchania i chuchania na jeden kawał rozwiązania, podczas gdy drugi kawał to paskudne spaghetti tylko dlatego, że "klient go nie uruchomi"? Nie!

Nad strukturą testów także trzeba myśleć. Planować hierarchię klas, stosować przemyślaną architekturę, poświęcać CZAS na podnoszenie jakości efektu naszej pracy.  Bardzo, bardzo często widzę testy składające się w 70-80% z kodu wytworzonego metodą kopiuj/wklej. Czy ich autor postąpiłby tak samo z kodem "nie-testującym"? A gdzie tam, przecież tak nie można. Skąd więc samo-przyzwolenie na takie olewanie tutaj, tylko dlatego że nazwa pliku wynikowego z VS kończy się na *.Tests.dll?

Jakiś kod przygotowujący środowisko się powtarza? To zamknijmy go w helperze / klasie bazowej / extension method / czymkolwiek.

Wykorzystywany framework nie zawiera pożądanej funkcjonalności? To dlaczego go nie rozszerzyć, zamiast w wielu miejscach stosować te same kilka linijek realizujących jakiś scenariusz?

Maintenance / refactoring

O testy trzeba dbać. Regularnie. Niedopuszczalna jest sytuacja, w której raz napisany test żyje sobie własnym życiem od momentu zapalenia po raz pierwszy zielonej lampki w runnerze.

W miarę uzupełniania testów nieustannie będziemy identyfikować scenariusze powtarzalne. Trzeba wtedy zachować się tak, jak byśmy zareagowali na podobne okoliczności projektem testowym – postarać się przeorganizować istniejące testy w taki sposób, aby proces dodawania kolejnych mógł skorzystać z już zawartej tam wiedzy i logiki. Ale też z głową!

Na szczególną staranność i uwagę zasługuje proces definiowania kolejnych testów. Procedura ta musi być bardzo prosta, błyskawiczna i niewymagająca wielkiego wysiłku umysłowego. Jeżeli jednak natrafimy na trudność przy tym kroku – tworzenia nowego testu – to oznacza, że trzeba w tym miejscu pozostać chwilę dłużej. Niepewność "gdzie mam ten nowy test utworzyć?" pokazuje, że w naszej hierarchii musimy zdefiniować nowe miejsce na dany typ testów wykonujących jakąś funkcjonalność. Nie można wtedy wsadzić go gdziekolwiek, bo po kilku dniach takiego postępowania – burdel murowany. Zawahanie "ale jak spreparować zależności dla tego nowego testu?" wynika z nie do końca jeszcze gotowego procesu konfiguracji/mockowania tego konkretnego kawałka funkcjonalności. Co robimy? Bynajmniej nie piszemy masy kodu konfiguracyjnego dla tego jednego nowego scenariusza. Zamiast tego szukamy innych testów powiązanych z tą częścią programu i analizujemy, czy ten jeden nowy klocek nie pasuje przypadkiem do testów już utworzonych.

I tak dalej….

A to wszystko także po to, żeby lektura testów była prosta i nieskomplikowana. Testy powinno się czytać bez ciągłego zastanawiania "a czemu tutaj tak a nie inaczej…?". Wszelkie "niejasne" elementy tworzenia testów powinny być schowane za odpowiednimi metodami. Ja na przykład regularnie stosuję konwencję zamykania konfiguracji mocków za extension methods z opisowymi nazwami. Na przykład 4-5 linijkowe ustawienie, że metoda "GetUserById(int id)" na interfejsie "IUsersRepository" zwraca użytkownika z ustawionym takim Id wyrzuciłbym z samego testu do metody, której wywołanie w teście wyglądałoby tak: var user = _usersRepository.configure_finding_user_by_id(3). A jeśli jakieś repo miałoby wyrzucić błąd dla danego parametru: _usersRepository.configure_error_for_finding_user(666). Takie helpery mogą znacznie zwiększyć czytelność testów.

Testy testów?

Zdarza mi się pisać kod pomocniczy, dedykowany dla testów, który jest nietrywialny. Jak to robię? Stosując TDD:). Może to brzmi głupio i "przesadnie"… ale nie ma w tym nic dziwnego. I, gdy już taka sytuacja występuje, faktycznie ułatwia pracę.

Wtedy jednak bynajmniej nie tworzę projektu xxx.Tests.Tests.dll. Zwykle w takim wypadku testy trzymam albo w tym samym pliku co testowany kod, albo w pliku obok – tak, aby jedno i drugie było zawsze blisko. Zawsze czysto. Zawsze pewnie.

Czy pisanie testów jest łatwe?

Nie. Pisanie testów nie jest łatwe. Wymaga dużo praktyki, wymaga popełnienia masy błędów, wymaga wiele czasu. Ale jest przyjemne. I na dłuższą metę – opłaca się. I piszę to nie z pozycji tego co rozumy pozjadał. Piszę to z pozycji tego, który widzi, że po długich latach czytania, pisania, próbowania, bicia głową w mur… chyba wreszcie zaczynam powoli łapać jak to należy robić. Mam przynajmniej taką nadzieję.

A skoro łapię ja, to załapać może każdy. W końcu ja Trylogię i Krzyżaków czytałem po 7-8 razy, podobno temu że przez pierwsze 6 nie rozumiałem o co chodzi:).

Share.

About Author

Programista, trener, prelegent, pasjonat, blogger. Autor podcasta programistycznego: DevTalk.pl. Jeden z liderów Białostockiej Grupy .NET i współorganizator konferencji Programistok. Od 2008 Microsoft MVP w kategorii .NET. Więcej informacji znajdziesz na stronie O autorze. Napisz do mnie ze strony Kontakt. Dodatkowo: Twitter, Facebook, YouTube.

3 Comments

  1. Ehhh… Mam wielką nadzieję, że kiedyś trafię do firmy, dla której TDD to standard ;). A jak TDD to i DI i ORM też. Brałem udział w projekcie systemu bankowego zarządzania transferami i przykro mi patrzeć na kod T-SQL, który zawiera całą logikę tej aplikacji. Pół roku od wdrożenia dalej wychodzą mniejsze, a czasem większe błędy (na początku było bardzo dramatyczne). Na pytanie czy ten system działa poprawnie można odpowiedzieć: właściwie, chyba, w sumie tak – i tyle. Nie ma ani jednej linijki kodu testowego, która udowadniała by, że ten system spełnia jakieś wymagania i, że działa zgodnie z założeniami … . Czy system był testowany ? Tak, był, na kilka dni przed wdrożeniem pracownicy biznesu klikali po UI jak by byli po LSD :)

  2. tipi,
    Przerażające jest to co piszesz, szczególnie w kontekście takiej aplikacji…
    A co do "firmy dla której TDD to standard" – nic się samo znikąd nie bierze, może warto wyjść z inicjatywą…? ;)

  3. [quote]Przerażające jest to co piszesz, szczególnie w kontekście takiej aplikacji… [/quote]
    Od kilku lat pracuje w firmach ktore tworza oprogramowanie dla instytucji finansowych i powiem ze zdziwilbys sie bardzo jak wyglada kod np agentow transferowych z ktorymi mialem/mam doczynienia – masakraaaaaaaaa