Kurs TDD cz. 1: Wstęp

Swój pierwszy wpis na blogu zacznę od części numeros unos cyklu poświęconemu Test-Driven Development. TDD w ostatnim czasie święci triumfy i to nie bez powodu. Dlaczego tak jest i czy gra jest warta świeczki – będę to wyjaśniać. W tym kursie znajdzie się miejsce na przedstawienie czym jest TDD, jak powinno się pisać według tej filozofii, jakie to ma wady i zalety. W dalszych częściach kursu przejdę do omówienia bardziej technicznych kwestii – napiszemy pierwszy test jednostkowy (unit test), omówię różnice między testami jednostkowymi, a integracyjnymi, przedstawię świat test-doubles i opiszę najlepsze narzędzia do testowania dla C#.

Czym TDD jest, a czym nie jest?

Na początek bardzo ważna kwestia, która może być niezwykle myląca—wbrew swojej nazwie, Test-Driven Development nie jest techniką służącą testowaniu. Nie chodzi o samo w sobie pisanie testów do kodu czy też powielanie pracy testera. TDD jest techniką programowania, jest techniką pisania i budowania kodu. W następnych akapitach wyjaśnię dlaczego.

Głównym celem test-driven jest:

  1. Zachowanie wysokiej jakości designu w swoich klasach.
  2. Uniknięcie złej interpretacji wymagań biznesowych.
  3. Zachowanie prostoty w kodzie: YAGNI + KISS.

W TDD nie chodzi o testowanie. Testowalny kod, a w konsekwencji automatyczny i natychmiastowy feedback o błędzie w systemie jest w TDD rzeczą dodatkową, ale nie nadrzędną. Kluczowym aspektem TDD jest pisanie testu przed napisaniem docelowego kodu. Można pisać testy równolegle w trakcie pisania logiki biznesowej, można też pisać testy po implementacji, ale to już wtedy nie jest Test-Driven Development. W TDD testy piszemy zawsze jako pierwsze, przed kodem.

Przyjrzyjmy się jeszcze raz powyższym trzem celom i rozwińmy je w kontekście tego co zostało już powiedziane.

  1. Napisanie testu przed implementacją kodu wymusza przemyślenie designu w naszym projekcie. Klasa w teście jednostkowym powinna być odizolowana od innych klas. Programista musi zatem zidentyfikować te miejsca, gdzie zależności (shims) występują i zaprojektować klasy z ich uwzględnieniem. TDD wymusza dobry design poprzez rozpoznanie interakcji i interfejsów między klasami. Uzyskujemy dzięki temu klasy luźno związane ze sobą (loosely coupled).
  2. Mówi się powszechnie, że testy jednostkowe są dokumentacją programisty. Dlaczego? Testy pisane są zawsze w kontekście dokumentacji. Dzięki temu, testujemy i implementujemy kod, który spełnia wymagania klienta (ta… ok, zgoda, ale zawsze możemy być bliżej niż nie-TDD) Niby oczywista rzecz, ale trzeba pamiętać o tym, że dokumentacja zawiera zazwyczaj wiele niejednoznaczności, które mogą być interpretowane na różne sposoby. Rozważając testy pod kątem wymagań musimy też zdefiniować punkty brzegowe każdego kryterium, np. dzielenie przez zero lub wpisanie litery zamiast liczby jako dane wejściowe do funkcji.
  3. YAGNI jest akronimem od You Aren’t Going to Need It, z ang. “nie będziesz tego potrzebować”, a KISS to Keep It Simple Stupid, czyli “to ma być proste, głupku”. W TDD nie piszemy testów i nie implementujemy kodu do rzeczy, których nie potrzebujemy teraz, a które być może ktoś będzie w przyszłości potrzebować. Jeśli kiedyś zajdzie potrzeba użycia nowej funkcjonalności, wtedy to napiszesz. Simple :)
    Więcej interesujących rzeczy o YAGNI możecie poczytać na http://c2.com/cgi/wiki?YouArentGonnaNeedIt.

Red-Green-Refactor

Kluczowym aspektem TDD jest cykl pisania testów. Najpierw piszemy testy, następnie implementujemy funkcjonalność, a na końcu refaktoruzyjemy.

Cykl nazywany jest najczęściej Red-Green-Refactor lub TDD Mantra, składa się z trzech etapów, które jako całość są powtarzane:

  1. Red: Piszemy test, który się nie powodzi.
    1. Testy piszemy do pustych, ale istniejących już klas i metod (dzięki czemu możemy korzystać z IntelliSense).
    2. Uruchamiamy test i oczekujemy, że się nie powiedzie.
  2. Green: Piszemy kod aby testy się powiodły.
    1. Implementujemy kod (według dokumentacji).
    2. Uruchamiamy testy. Wszystkie testy muszą się powieść.
  3. Refactor: Refaktoryzacja kod—wprowadzenie zmian, które poprawiają jakość kodu (np. usunięcie duplikacji), ale nie zmieniają jego funkcjonalności.
    1. Po refaktoryzacji, uruchamiamy wszystkie testy by sprawdzić czy czegoś nie zepsuliśmy.
    2. Ten punkt jest często lekceważony lub pomijany w procesie. Nie zapominajmy o tym, równie ważnym co dwa poprzednie, elemencie.
red-green-refactor-diagram

W TDD najpierw piszemy testy, później kod, a na koniec refaktoryzujemy.

Wady i zalety TDD

Zalety TDD:

  • Dokładne zrozumienie wymagań dokumentacji. Testy piszemy zawsze względem dokumentacji.
  • Testy jako dokumentacja jest zawsze aktualna w czasie.
  • Testy nie wprowadzają niejednoznaczności, cechy którą może posiadać dokumentacja papierowa.
  • Wymuszanie dobrego designu kodu i szybka identyfikacja potencjalnych błędów w designie, np. problem z zależnościami.
  • Lepsza zarządzalność kodu w czasie.
  • Łatwiejsze i bezpieczniejsze łatanie kodu.
  • Natychmiastowy i automatyczny feedback na temat błędu w kodzie.
  • Testy regresyjne pozwalają stwierdzić czy po naszych zmianach nie zepsuliśmy przy okazji czegoś w innej części systemu.
  • Krótszy, całkowity, czas procesu developmentu.
  • Dużo mniej ręcznego debugowania.

Wady TDD:

  • Czas i wysiłek na trening i przygotowanie developerów.
  • Potrzeba dyscypliny osobistej i zespołowej. Testy muszą być zarządzane i poprawiane w czasie w taki sam sposób jak cała reszta kodu.
  • Początkowa percepcja dłuższego czasu developmentu.
  • Nie wszyscy menadżerowie dają się przekonać. Biją argumentem dwukrotnie dłuższego developmentu, choć całkowity czas trwania developmentu (wliczając szukanie i naprawę błędów, nie tylko pisanie kodu) w TDD jest krótszy niż w nie-TDD.

TDD, a agile

W obiegowej opinii Test-Driven Development jest częścią agile. Błąd! Agile i TDD to dwie rozłączne sprawy, obydwie istnieją niezależnie. Agile manifesto nie wspomina ani o TDD, ani o testach jednostkowych. Agile mówi o kodzie testowanym, natomiast nie określa czy kod ma być pisany przed-po-czy w trakcie pisania kodu. TDD natomiast tak—testy powinny być pierwsze.

TDD może być, i bardzo często jest, procesem współbieżnym z agile, ale należy pamiętać, że nie jest i nie był jego częścią.

Podsumowanie

W Test-Driven Development nie chodzi o… testowanie, a prostotę i design.

Dla programisty chcącego zacząć pisać według TDD najważniejsze jest zrozumienie cyklu Red-Green-Refactor. Testy piszemy w pierwszej kolejności, następnie piszemy implementację kodu, a następnie dokonujemy refaktoryzacji. Test-first jest zatem główną ideą tej filozofii.

Kod pokryty testami jednostkowymi jest równocześnie najbardziej aktualną formą dokumentacji.

Korzyści płynące ze stosowania TDD zostały udowodnione na przykładzie wielu projektów (będzie odrębny wpis traktujący o tym). Programista piszący po raz pierwszy odczuwa dyskomfort płynący z podwójnie dłuższego procesu pisania kodu, jednak kod ten jest zredukowany w całym procesie, gdyż w rezultacie finalny kod jest czystszy i zawiera mniej błędów. A to wszystko dzięki technice TDD!

20 thoughts on “Kurs TDD cz. 1: Wstęp

  1. Pingback: dotnetomaniak.pl
  2. Pingback: Kurs TDD część 2: Testy jednostkowe, a testy integracyjne | DariuszWozniak.NET
  3. Bardzo fajny wstęp. TDD na tyle mnie zainteresowało, że postanowiłem poświęcić mu całą pracę dyplomową :)

  4. Pingback: Kurs TDD część 3: Struktura testu, Act-Arrange-Assert | DariuszWozniak.NET
  5. Pingback: Kurs TDD część 4: Nasz pierwszy test jednostkowy | DariuszWozniak.NET
  6. Pingback: Kurs TDD cz. 4: Nasz pierwszy test jednostkowy | DARIUSZ WOZNIAK.NET
  7. Pingback: O metodologii TDD (Test Driven Development) | IT w kajzarowie
  8. Witam, interesujący wpis ale chciałbym dodać swoje 2 grosze na odnośnie zdania: “(…) najważniejsze jest zrozumienie cyklu Red-Green-Refactor”. Ogólnie zgadzam się, że jest to dość ważna część ale powiedziałbym, że ważniejsze jest zrozumienie tego co testować a czego nie.
    Z doświadczenia wiem, że programiści (zazwyczaj Ci mniej doświadczeni) mają tendencję do testowania nie tego jak zachowuje się Unit ale jego konkretnej implementacji. Prowadzi to do testów zbyt związanych z implementacją, a co za tym problemy podczas refaktorowania kodu.

    Jakie jest Pana zdanie na ten temat?

    • Zgadzam się z Panem, dlatego bardzo ważna jest faza projektowania API. Jeśli dobrze zdefiniujemy publiczne API na samym początku, to nie będziemy mieć większych problemów z późniejszą refaktoryzacją i ew. aktualizacją testów. Tak jak Pan wspomniał, wymaga to czasu i obycia z TDD.

  9. Czy to znaczy, że TDD mogę wykorzystać tylko w jezykach obiektowych? Jak to sie ma do programowania w VBA lub ASM? W artykule nie jest dla mnie jasno napisane kiedy faktycznie ta technika może być wykorzystana. Co to znaczy pisać testy – programujac w IDE czy typowe test casy? Co jezeli w IDE nie ma takiej funkcjonalnosci?

  10. Pingback: KurS TDD cz. 23: Czy to się opłaca? | DariuszWoźniak .NET
  11. Pingback: Podsumowanie kursu TDD | DariuszWoźniak .NET

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s