Jeszcze słowo o TDD

O TDD napisano wiele, sam napisałem całkiem sporo i mówiłem całkiem sporo podczas kilku prelekcji. Używam TDD od ponad 5 lat już. Powinienem być super mega ninja pro TDD master. Mimo tego jakiś czas temu, pisząc bardzo prosty kod, na prawdę super prosty, naszła mnie taka refleksja:

Jest zielone, jest ok.

[Fact]
public void Example_test()
{
    _pinger.Ping().Returns(c =>
    {
        throw new Exception();
    });
    Assert.DoesNotThrow(()=>_monitor.CheckAvaliability());
    _destinationSystem.Received(1).Ping(Arg.Is<IDiagnostics>(diag => diag.State == ""));
}

Powyższy kawałek kodu używa NSubstitute (.Ping().Returns(c=>…..) do mockownia. Oraz XUnit-a do testów. Napisałem test jak wyżej, napisałem kod. Niby wszystko zielone, niby ok. Jakimś cudem jednak pokusiło mnie, żeby dorzucić AutoData. AutoData to taki sprytny atrybut, który pozwala generować dane testowe. W moim przypadki Message z wyjątki ma znaczenie, dlatego stwierdziłem, że Message można spokojnie generować – tutaj kod jest taki, że mam pudełko, które jak zrobi coś złego to to złe ma wyjść z drugiej strony – case dla AutoData jak malowany.

[Theory,AutoData]
public void Example_test(string exceptionMessage)
{
    _pinger.Ping().Returns(c =>
    {
        throw new Exception(exceptionMessage);
    });<
    Assert.DoesNotThrow(()=>_monitor.CheckAvaliability());
    _destinationSystem.Received(1).Ping(Arg.Is<IDiagnostics>(diag => diag.State == exceptionMessage));
}

No i zonk!!! Test już nie bardzo to przechodzi. Pomyślałem, bez jaj, przecież implementacja jest tak prosta, że tego nie da się zepsuć – nawet nie wiem po co pisałem na to test, ale nie uprzedzajmy faktów…. Debug testu i….. okazało się, że jednak w kodzie gdzie łapię wyjątek, to oprócz logowania tak jak to powinno być to już message-a  nie przepisuję. Kto by pomyślał.

Testy jednostkowe nie dają 100% gwarancji, że powstanie kod bez bugów, nie dają nawet jakiejkolwiek gwarancji, co jednak dają, to dużo wyższą szansę, że powstały kod będzie miał mniej bugów niż bez testów. W moim przypadku, okazało się, ze prosta refaktoryzacja – lub bez AutoData dodanie kilku przypadków testowych, pozwoliła złapać buga zanim kod poszedł na produkcję.

TDD is dead czyli telenowela dla jajogłowych

David H Hannson, autor Ruby on Rails opublikował artykuł: http://david.heinemeierhansson.com/2014/tdd-is-dead-long-live-testing.html no i poszło… mleko się rozlało. Burza większa niż o sosnę (tą rozdartą – nie o brzozę, brzozy są niepolityczne).

tdd

Uncle Bob napisał http://blog.8thlight.com/uncle-bob/2014/05/11/FrameworkBound.html ale jak wieść gminna niesie to jest wersja politycznie poprawiona bo czyjeś uczucia religijne zostały obrażone – oryginalna wersja jest jeszcze w feedly i/lub cache googla. Generalnie wydarzyło się to tak szybki, że zanim przeczytałem artykuł to był już zmieniony – dobrze że koledzy z zespołu mi o tym powiedzieli bo dzięki nim przeczytałem oba te teksty.

Potem wielka debata Is TDD Dead z Martinem Fowlerem, Kentem Beckiem i Davidem Hannsonem, o której chyba pół internetu grzmiało (https://plus.google.com/events/ci2g23mk0lh9too9bgbp3rbut0k).

Następny odcinek czyli DHH problem http://codon.com/the-dhh-problem

W pracy pół poniedziałku TDD i tego samego dnia polska grupa celebryto.netowa wciągnęła się w telenowelę.

learn to use tdd

Co będzie dalej, gdzie ta telenowela się skończy? Podobno – znowu wieść gminna niesie że w 4 odcinku debaty Fowler – Beck – Hannson ma się okazać, że DHH jest synem (lub ojcem) Uncle Bob-a.

lodowka

Ci co oglądali South Park i pamiętają odcinek z piekłem i niebem wiedzą, że prawidłowa odpowiedź przyjdzie po śmierci (spoiler:  mormoni). Dla mnie na tą chwilę faktem jest to, że TDD jest trudne do ogarnięcia, ale nie trudne w podstawach bo te są łopatologicznie proste. Można je ogarnąć w kilka godzin czy dni. Problem jest taki, że wymaga to olbrzymiej determinacji od programisty oraz ciągłego trzymania się metodyki. Linijka po linijce, kawałek po kawałki dzień po dniu nabierać trzeba doświadczenia w TDD. Dzięki temu już po kilku latach jest szansa, że będziemy mieli wyczucie, kiedy używać TDD a kiedy nie, kiedy pisać wór testów na jedną mała klasę a kiedy napisać 5 testów na wór klas. Unit Testing testuje UNIT czyli jednostkę – jednostka to nie 1 klasa  czy 1 metoda, to może być zbiór klas. Żeby to czuć, kiedy iść w tą a kiedy w inną stronę to trzeba mieć dużo doświadczenia. Wydaje mi się, że czuję to, po ponad 4 latach z TDD (w różnym natężeniu), wydaje mi się, że to potrafię mimo, że jest jeszcze masa do nauczenia przedemną. Jedno jest pewne, TDD nie tworzy architektury i jej nie polepszy. TDD jedynie może zdegradować architekturę jeśli postawimy ołtarzyk tej metodyce. TDD to świetne narzędzie, które wymaga wprawy i umiejętności użycia.

Wg. mojej percepcji świata, TDD to przyszłość, to potężna umiejętność i warto w to inwestować. Jeśli mam rację to super, bo wszyscy przeciwnicy TDD odpadną z rynku i będzie jeszcze więcej miejsca. Jeśli się mylę, no cóż, zawszę mogę usunąć testy i wrócić do mozolnego „przeklikiwania” kodu lub zostać skrytym anonimowym tdd-owcem. Życie pokaże. Tymczasem czas iść do lodówki i mieć nadzieję, że telenowela tam nie zawitała, mieć nadzieję, że ostatni bastion spokoju u chłodu ostał się.

TDD i popularne wymówki

Od ponad 3 lat używam TDD z lepszymi i gorszymi wynikami, zawsze koniec końców okazuje się, że kod napisany w TDD (nie TAD czyli nie dopisując testy post factum ale w prawdziwym TDD czyli pisząc testy NAJPIERW) sprawia najmniej problemów i jest najłatwiejszy do utrzymania i do modyfikacji. Czyli wydaje się, że TDD to świetne narzędzie, dlaczego zatem tak mało osób stosuję tą metodykę? Poniżej postaram się zebrać co popularniejsze wymówki. Jeśli macie swoje to proszę dopisywać.

1) Pisanie testów wydłuża programowanie/ trzeba napisać dwa razy tyle kodu

No cóż, to fakt, że trzeba napisać prawie 2x tyle kodu ale to nie jest jednoznaczne z tym, że programowanie trwa dwa razy dłużej. Proces programowania nie polega na klepaniu kodu jako takiego, klepanie kodu zajmuje najmniej czasu. Najwięcej czasu zajmuje obmyślenie tego co chcemy napisać. Cały ten proces odbywa się w głowie i przez TDD nie wydłuża się.

2) Pisanie testów podraża produkcję oprogramowania (bo przecież trzeba napisać 2x więcej)

Bzdura nad bzdurami. Koszt produkcji oprogramowania to nie tylko naklepaniu kodu, to wszystko co się dzieje od pomysłu do zakończenia wdrożenia u klienta. No i biorąc to pod uwagę, jeśli poświęcimy 2x albo nawet i 3x więcej czasu na pisanie w TDD (nie, żeby TDD wymagało 3x czasu – ale weźmy przypadek skrajny) ale dzięki temu otrzymamy kod, który ma 10x mniej błędów a poprawienie każdego z nich zajmuje ~15minut to okaże się, że pisanie bez testów – tak z buta tak naprawdę podraża koszt produkcji kilkukrotnie. Co z tego, że szybko napiszemy jeśli później, za tydzień lub dwa spędzimy cały dzień na poprawianiu błędów? Krótko terminowo TDD wymaga więcej energii i czasu (szczególnie zanim nauczymy się pisać dobre i wartościowe testy) jednak w dłuższej perspektywie taki kod generuje mniej bugów, mniej zgłoszeń i w efekcie czego kosztuje znacząco mniej pieniędzy.

3) Nie piszę testów bo to taki mały testowy projekcik – taki na szybko

Chyba jedna z większych pułapek. Po co myśleć nad architekturą i skupiać się na TDD jeśli ten prosty i banalny problem napiszę w kilka godzin, wszak jestem takim kozakiem, że przy tej pierdołce nie zrobię błędów. Nie jeden kozak rzucał mięsem nad takim projekcikiem bo częściej okazuje się, że trzeba dopisać jeszcze to i tamto i jeszcze coś i nagle projekt zaczyna być znaczący… tyle tylko, że wtedy zaczyna się zabawa z gów… eee… mamy bagno, a najgorsze w tym jest to, że często w takim projekcie już nie pojawiają się dobre wzorce – No bo to już tak ktoś napisał więc trudno…. Moim zdaniem, jeśli na 100% wiesz, że to będzie eksperyment to pisz bez testów, sprawdź co chciałeś a potem wywal całość. Jeśli musisz spędzić nad projektem kilka dni to lepiej zakasać rękawy i TDDzić od początku.

4) Mój klient/prezes/dyrektor/kierownik nie pozwala mi pisać testów

Serio? Nie uwierzę, że Twój klient może zabronić pisać testów, nie uwierzę, że prezes/dyrektor/kierownik przyjdzie i powie, nie pisz testów, pisz kiepski kod, który nie wiadomo co będzie robił. Jeżeli jakimś dziwnym trafem jednak znajdziesz się w takiej sytuacji, kiedy zabronią Ci pisać w TDD, jeśli zabronią Ci stosować dobre praktyki to jest to sygnał aby uciekać z firmy bo dzieje się źle. Jest jeszcze inny aspekt tej sprawy, aby pisać testy nie musisz mieć czyjejkolwiek zgody, możesz pisać je samemu we własnym zakresie, możesz je trzymać w osobnym projekcie i commitować do własnego repozytorium (np. gitowego) i tak wiem, że w niektórych korporacjach są bardzo restrykcyjne zasady, ale jeśli trafiłeś w takie miejsce to odpowiedz sobie na pytanie. Czy chcesz pracować niczym galernik na statku, nie mieć możliwości wyrażenia swojego zdania nawet w sytuacji, kiedy wiesz, że Twoja galera zbliża się do wodospadu?

5) Próbowałem pisać testy ale każda drobna zmiana powodowała, że setki testów przestawały działać

Jeśli drobna zmiana czy refaktoryzacja w kodzie spowodowała zaczerwienienie się setki testów to znaczy, że robiłeś to ŹLE. No cóż, nikt nie rodzi się z umiejętnością kodowania i nikt nie rodzi się z umiejętnością pisania dobrych i wartościowych testów jednostkowych. Trzeba czasu aby nauczyć się tego dobrze. Trzeba pisać testy aby nauczyć się pisać je w prawidłowy sposób. Nikt nie nauczy Cię tego za pomocą poradnika TDD dla opornych czy TDD w weekend. Niestety trzeba przysiąść i ćwiczyć.

6) Mockowanie jest trudne

Jest wręcz przeciwnie, mockowanie jest banalnie proste w swojej koncepcji. Jednak potrafi być ono trudne jeśli projekt systemu jest delikatnie mówiąc zepsuty. Pisząc bez TDD bardzo łatwo napisać kruchy monolit, którego testowanie jest prawie awykonalne. Nie znaczy to jednak, że mockowanie jest trudne – znaczy to tylko tyle, że trzeba podszkolić się z budowania elastycznego i otwartego na zmiany kodu.

7) Nie mamy czasu na pisanie testów

Chyba najczęstsza wymówka z jaką się spotkałem. Co ciekawe najczęściej brak czasu spowodowany jest tym, że pół dnia poprawia się błędy, które przecież nie pojawiły się w kodzie same. Czyli pół dnia poprawiamy błędy zamiast pisać nowy kod, fascynujące a jakie motywujące przy okazji. Wyobrażasz sobie sytuację, że np. cały etat jednego człowieka w zespole poświęca się na to aby poprawiał błędy? Motywacja do pracy musi sięgać zenitu. Zamiast zainwestować w metodykę pracy, która daje lepsze rezultaty zmarnujmy energię na znalezienie wymówki aby tego nie robić a potem wróćmy do swojego kieratu pod tytułem szybko napisać kod byle jak byle by był bo przecież trzeba trzeba poprawiać błędy. Logika wprost powalająca na kolana nieprawdaż?

8) Później dopiszę testy

Taa.. jasne. Pomijając drobny szczegół czyli jaki wpływ na kod ma samo TDD, to najczęściej nie będzie się nikomu chciało dopisywać testów do czegoś co przecież już działa. Inna sprawa, że jeśli jednak dopisze się testy, to mają one skłonność do testowania implementacji a nie zachowania to po pierwsze a po drugie, jeśli kod zawiera jakieś błędy, to takie testy post factum często (chociaż nie zawsze) po prostu cementują błąd na zawsze.

Uncle Bob mówi, że pisanie testów dla programistów jest jak mycie rąk przez lekarzy przed operacją. Jest to kwestia profesjonalizmu i bez względu co powie pacjent i pod jaką presją działa lekarz to zawsze jakoś ma czas na umycie rąk przed zabiegiem. Tak samo powinni postępować programiści. Na to stwierdzenie usłyszałem kiedyś, że to jest przesada bo to co innego, bo inna branża…… itd. Życie podało mi piękny kontrargument. Firma tworząca oprogramowanie dla branży security, “… wiesz, jak sygnał z np. napadówki nie dotrze do centrali to może być zagrożone ludzkie życie”. Nie piszesz tego typu systemów? Piszesz przysłowiowe systemiki do fakturek? Skąd wiesz, jak zareaguje księgowa albo prezes firmy, który używa twojego oprogramowania, gdy z powodu drobnego błądziku w kodzie przyjdzie skarbówka przetrzepać wszystkie papiery w tej firmie?

A z jakimi wymówkami Wy się spotkaliście lub jakie macie aby nie pisać w TDD?

Dlaczego warto pisać testy jednostkowe

Dzisiaj będzie gadka motywacyjna o pisaniu testów więc jeśli jeszcze nie piszesz, może to Ciebie w końcu przekona.

We wpisie Testowanie własnego ControllerFactory w MVC pisałem jak to zmarnowałem 6h na napisanie praktycznie jednego testu do kawałka kodu, który można napisać w 15 minut i przez dodatkowe 15 minut prze-klikać w kilku różnych przypadkach. Gdzie tu logika, gdzie tu sens. Pisząc CustomControllerFactory nie pisze się go z byle powodu, pisze się, ponieważ są potrzebne bardzo specyficzne wymagania. Pisząc kod wiemy dokładnie co robi każda linijka, wiemy po co jest każdy jeden if i dlaczego wartość trzeba pomnożyć razy 0.192 co 10te obliczenie. Jednak co się stanie z tą wiedzą za tydzień albo za miesiąc czy za rok? Na prawdę będziesz wiedzieć co to jest to wspomniane 0.192? Będziesz wiedzieć co robi flaga isLocal = false? Założę się, że 90% tego typu wiedzy wyparuje. Więc jeśli po roku przyjdzie nam napisać nową funkcjonalność lub poprawić buga, jest olbrzymia szansa, że zupełnie nieświadomie coś zepsujemy bo już nie będziemy pamiętali dlaczego coś zostało napisane tak a nie inaczej. Jeśli jednak napiszemy testy to ta wiedza jest zawarta w testach. Nawet i za 20 lat uruchamiając testy będzie można sprawdzić czy założenia są spełnione i to bez względu na to czy kod po drodze był refaktoryzowany czy nie. Niezły luksus. Jeśli to nie przekonuje Cię do testów jednostkowych to mam jeszcze jedną historię z życia wziętą:

Napisałem mechanizm wczytywania licencji w C#… z pliku tekstowego generowanego w Delphi… z testami jednostkowymi. Generalnie lodzio miodzio, wszystko działa. Do czasu aż nie okazało się, że polskie literki nie bardzo działają. Gdzieś się tablice bajtów nie zgadzały, licencja się nie validowała, ogólnie masakra. Trzeba poprawić. Wygenerowana została testowa licencja z polskimi znaczkami, która nie działała. Dopisany został nowy test z tą licencją – test nie przechodzi. Po w sumie 15 minutach, jest… ta jedna linijka kodu (ave SOLID) gdzie małe hokus pokus z encodingami i bam…. licencja działa, validuje się. Super! ale teraz trzeba będzie wygenerować nowe licencje dla starych klientów no bo nowa wersja z taką zmianą zepsuje się na starych plikach…. i tu oświecenie. Przecież dopisaliśmy nowy test z polskimi literkami, a przecież stare testy się nie zmieniły i dalej działają, czyli poprawka działa na nowe a nie psuje starego. Hura.. w 15 minut zakończone zadanie. Nie trzeba było generować iluś tam licencji, wgrać, konfigurować, prze-klikać. Testy zaoszczędziły co najmniej godzinę mojego czasu i nie wiem ile czasu wdrożeniowca. I jak tu wierzyć komuś, kto mówi, że pisanie testów kosztuje 2x tyle czasu co pisanie bez testów.

Start-up trap czyli płapki startupów

Uncle Bob opublikował świetny artykuł pod tytułem Start-up trap 

No i się zaczęło i zawrzało w programerskich czerepach. Sporo szumu zrobiło się na twitterze. Greg Young napisał ciekawy wpis na swoim blogu http://goodenoughsoftware.net/2013/03/06/startups-and-tdd/ ale tutaj to już się wylało:

https://news.ycombinator.com/item?id=5325491 (Uwaga, długie Smile with tongue out)

Koniec końców Uncle Bob napisał kolejny wpis

http://blog.8thlight.com/uncle-bob/2013/03/06/ThePragmaticsOfTDD.html

Myślę, że warto przeczytać na spokojnie bo wprawdzie to nie kończy dysputy pomiędzy fanami tdd a przeciwniko-olewaczami tdd  to warto znać argumenty obu stron i znać ogląd z obu stron barykady.