TDD czyli Test Driven Development

tdd

Jakiś czas temu pisałem o TDD czyli o test driven development. Dzisiaj napiszę po co w ogóle bawić się w takie bezsensowne nadmiarowe rzeczy.

Wikipedia definiuje to zagadnienie dosyć enigmatycznie:

Test-driven development (TDD) jest techniką tworzenia oprogramowania zaliczaną do metodyk zwinnych (Agile). Pierwotnie była częścią programowania ekstremalnego (ang. extreme programming), lecz obecnie stanowi samodzielną technikę. Polega na wielokrotnym powtarzaniu kilku kroków:

  1. Najpierw programista pisze automatyczny test sprawdzający dodawaną funkcjonalność. Test w tym momencie nie powinien się udać.
  2. Później następuje implementacja funkcjonalności. W tym momencie wcześniej napisany test powinien się udać.
  3. W ostatnim kroku, programista dokonuje refaktoryzacji napisanego kodu, żeby spełniał on oczekiwane standardy.

Źródło: http://pl.wikipedia.org/wiki/Test-driven_development

Po takim opisie można dojść do wniosku, że jest to tylko zbędna nadmiarowość. Można również stwierdzić, że jeżeli nie korzystamy z technik zwinnych to nie musimy korzystać z TDD. Czy tak jest naprawdę? Obdzierając TDD z całej filozoficznej otoczki w stylu programowanie extremalne (XP) czy Agile (jeżeli nie jesteś agile to niestety znaczy że jesteś z epoki dinozaurów i nie nadajesz się do obecnych czasów 😉 bo przecież tylko agile jest cool) to pozostaje nam samo TDD. Pozostaje tylko test driven development czyli programowanie sterowane testami.

TDD krok po kroku

Zobaczmy jak wygląda proces tworzenia programu wg. metodyki TDD:

  1. Piszemy test jednostkowy, który będzie testował funkcjonalność, której jeszcze nie mamy. Oczywiście taki test się nie skompiluje więc po co go pisać? Pisząc taki test musimy zastanowić się nad tym, co naprawdę chcemy napisać. Zaczynamy projektować również sposób interakcji z kodem, który dopiero powstanie. Koniec końców tworzymy sposób interakcji w oderwaniu od samej implementacji.
  2. Napisaliśmy test, którego napisanie zajęło czas i w dodatku powoduje, że nasza aplikacja się nie kompiluje…. Owszem, ale czas poświęcony na napisanie testu, pozwolił nam zagonić szare komórki do przemyślenia problemu czy tego chcemy czy nie. Być może spojrzeliśmy na problem inaczej, głębiej, szerzej. Nawet jeśli nie, to mając obecne narzędzia jak Visual Studio, InteliJ, Resharper, Eclipse etc, mamy możliwość wygenerowania kodu na podstawie tego co już napisaliśmy. I tak w Visual Studio (lub Visual Studio + ReSharper) za pomocą kilku klawiszy możemy wygenerować wszystkie klasy oraz metody, które użyliśmy w naszych testach a których jeszcze nie napisaliśmy. To prowadzi do następnego kroku,
  3. Projekt się kompiluje ale nie przechodzi testów. No cóż, teraz pozostaje napisać logikę do przygotowanych w punkcie drugim pustych metod. W tym momencie po prostu tworzymy kod, tak jak byśmy to zrobili w podejściu zupełnie ad hoc.
  4. Napisaliśmy kod, testy jednostkowe przechodzą, co dalej…. Dalej purystyczne podejście mówi, że powinniśmy zrefaktoryzować kod tak aby był lepszy, szybszy czytelniejszy – ogólnie aby spełniał oczekiwane standardy. W tym miejscu pojawia się pierwszy raz moc testów jednostkowych. Zmieniając kod czy wręcz przepisując go na zupełnie nową i lepszą implementację musimy mieć pewność, że wprowadzane zmiany nie sabotują istniejącej funkcjonalności. Zmiany w kodzie wynikające ze zwiększania wydajności czy czytelności kodu nie mogą nie powinny powodować błędów w aplikacji ani nie powinny usuwać istniejącej funkcjonalności. Testy jednostkowe (o ile są sumiennie wpisywane) pozwalają nam z bardzo dużym prawdopodobieństwem przyjąć, że wprowadzane zmiany w kodzie programu nie powodują zmiany funkcjonalności. Nie przekonany? Jeżeli wczoraj pisałeś kod to pewnie dzisiaj pamiętasz co on robi i po co te wszystkie parametry i zmienne co jeżeli było to pół roku wstecz? Albo rok i więcej? W takiej sytuacji testy pozwalają zaoszczędzić masę czasu, dają nam zwiększoną pewność, że wprowadzane zmiany nie zaburzają istniejącego kodu.
  5. Tworząc testy jednostkowe otrzymujemy jakby zupełnie przy okazji dokumentację i to taką, która jest bardzo aktualna. W jaki sposób? Wyobraźcie sobie, że kolega napisał bibliotekę – my chcemy jej użyć. Jak to zrobić? Możemy albo go zapytać jak działa biblioteka(przeszkadzając mu i marnując jego czas) albo przeczytać dokumentację (jeżeli została w ogóle stworzona) lub możemy prześledzić kod i spróbować zrozumieć „co autor miał na myśli”. Mając testy jednostkowe możemy bardzo szybko podejrzeć jak wykorzystać bibliotekę. W końcu aby przetestować jakąś klasę, autor testu musiał użyć tej klasy. Z takich testów – jeżeli są sumiennie prowadzone – możemy dowiedzieć się bardzo dużo.
  6. Kolejny sposób w jaki odwdzięczają się testy jednostkowe to błędy. Nie oszukujmy się, nie ma oprogramowania bezbłędnego a testy jednostkowe nie spowodują, że nagle tworzone oprogramowanie będzie bezbłędne (wprawdzie zmniejszy ilość tzw. Idiotycznych błędów). Testy jednostkowe mądrze użyte pozwolą nam na pilnowanie aby raz poprawione błędy nie powracały – zmora wielu programów. Wyobraźmy sobie sytuację, kiedy otrzymujemy jakiś błąd do poprawienia. Napiszmy najpierw test jednostkowy, który pozwoli wyłapać ten błąd. Dla przykładu, kalkulator przy dzieleniu przez 0 wyrzuca obrzydliwy wyjątek. Napiszmy zatem test, który dzieli przez zero i sprawdza czy wyrzucony zostaje wyjątek. Teraz należy poprawić błędne działanie programu tak, aby działał prawidłowo a wszystkie testy jednostkowe (oprócz naszego testu na błąd) przechodziły bezbłędnie. Teraz co nam pozostaje, to zanegowanie testu – czyli jeżeli poprzednio wyrzucał wyjątek, to teraz ma nie wyrzucać wyjątku. Takie podejście powoduje, że po poprawieniu błędu mamy pewność, że taki sam błąd nie przedostanie się do wersji finalnej. Pamiętać należy, że im później wyłapiemy błąd tym więcej on nas kosztuje zarówno pod względem czasu jak i pod względem pieniędzy. Jeżeli pozwolimy aby błąd wymknął się do klienta, wówczas tracimy podwójnie – ponieważ klient zgłasza błąd do infolinii/helpdesku/etc, ci zgłaszają błąd zespołowi programistycznemu (często kierownikowi, który przydziela później zadanie) a po konsylium ktoś, w końcu go poprawia – to z jednej strony a z drugiej strony cierpi na tym nasze dobre imię. Przedstawione powyżej podejście do błędów i testów jednostkowych pozwala nam zabezpieczyć się przed nawracającymi błędami. Dodatkowo z czasem nazbieramy potężną bazę testów pochodnych błędów co stworzy nam swego rodzaju bazę testów regresyjnych.

Testy jednostkowe są jak dobra lokata. Na początku kosztują nas dodatkowy czas (dobre lokaty nas nie kosztują więc może te lokaty to nie za dobry przykład) jednak z czasem oddają ten czas przez bardzo dobre wspieranie nas – programistów.

TDD i jego wady

Czy są zatem jakieś wady tych wspaniałych, wszystko rozwiązujących, testów jednostkowych? Tak! Ja widzę 3:

Po pierwsze wymagają przestawienia się i pokonania wewnętrznego oporu przed zmianą. Proponuję zrobić jakiś niewielki projekt od początku wg TDD i zobaczyć, że pierwszy tydzień nie będzie łatwy. Jednak po przetrwaniu pierwszego tygodnia zaczniesz zauważać pozytywne zmiany.

Po drugie testy jednostkowe mogą wprowadzić nas w błędne koło pisania kodu pod test i testów pod kod bez zwracania uwagi na cel nadrzędny – stworzenie oprogramowania. Pamiętać należy, że to nie jest sztuka dla sztuki, TDD to tylko i wyłącznie narzędzie, które ma nam służyć. Piszemy testy do jasno sprecyzowanej funkcjonalności a nie same dla siebie – to nie jest wprawka w kodowaniu. To takie samo narzędzie jak debugowanie czy profilowanie.

Po trzecie poznając narzędzia z kategorii CodeCoverage czyli pozwalające pokazać pokrycie kodu testami jednostkowymi (takie jak np. NCover o czym będę pisał później) można wpaść w manię osiągania 100% pokrycia kodu testami. Oczywiście nie ma nic złego w 100% pokryciu, jednak jeżeli to będzie 80-90% to już jest bardzo dobrze. Nie warto marnować czasu na osiągnięcie tych 100% jeżeli czas jest w deficycie. Mając pokrycie na poziomie owych 80-90% można posłużyć się narzędziami, które same znajdą testy na brakujące 10%. Pamiętać należy, że (tak jak w punkcie drugim) TDD to tylko narzędzie a nie religia. To narzędzie ma nam służyć a nie my jemu.

Jeżeli macie jakieś pytania odnoście TDD to pytajcie w komentarzach. Postaram się na wszystkie odpowiedzieć.