Tuesday 11 January 2011

Test Driven... Design?

Ostatnio miałem kilka dyskusji na temat testów jednostkowych oraz Test Driven Development.

Postaram się omówić główne punkty mojej argumentacji za stosowaniem testów jednostkowych oraz metody TDD, którą wolę tłumaczyć jako Test Driven Design.

1. "Nie ma dowodów, że w ten sposób znajduje się więcej błędów"

TDD nie służy do znajdowania błędów (przynajmniej nie taki jest jego główny cel). Jego przeznaczeniem jest projektowanie aplikacji, tzn. pisze się najpierw test, służący specyfikacji fragmentu funkcjonalności, który mamy zaimplementować. W tym sensie zestawy testowe są wykonywalnym zamiennikiem dla dokumentów tzw. Low Level Design, gdzie jest opis słowno - muzyczny i diagramy UMLowe.

2. "TDD cierpi na tzw. "syndrom ojcostwa" - jeśli ktoś pisze testy, to robi te same założenia, co pisząc kod. Normalniejsze podejście jest takie, że testy pisze jedna osoba, a kod druga".

TDD nie służy do znajdowania luk i błędów w naszym rozumieniu wymagań. Od tego są inne metody testowania. TDD służy jedynie do upewnienia się, że kod robi to, co chcemy, żeby robił (bez względu, czy jest to zgodne z wymaganiami, czy nie). Owszem, fakt że piszemy testy tylko w oparciu o wymagania pomaga nam nie czynić założeń oraz druga osoba może potem przejrzeć scenariusze testowe i zweryfikować ich poprawność - w tym sensie weryfikuje nasz projekt.

4. "TDD to strata czasu - można przecież od razu pisać kod".

a) Jak wspomniałem, można traktować zestawy testów jednostkowych jako wykonywalne dokumenty LLD. Warto zadać sobie pytanie, czy łatwiejszy i mniej czasochłonny w pisaniu i utrzymywaniu jest taki dokument (który czasami potrafi mieć nawet 60 stron A4), czy zestaw testów, które dodatkowo weryfikują kod.

b) Skłaniam się do traktowania również procesu puszczenia testów jednostkowych jako elementu kompilacji. Kompilacja kodu np. w C++ również zajmuje sporo czasu, natomiast wielu z nas nie chciałoby oddawać gwarancji uzyskanych przez sam fakt, że kompilujemy nasz kod (zazwyczaj jest to sprawdzanie typów, statyczna analiza kodu itp.). Bruce Eckel zauważył nawet, że w przypadku języków interpretowanych, takich jak Python, posiadając zestaw testów jednostkowych pokrywających znaczny obszar kodu, wcale nie tęskni za kompilacją kodu, bo jeśli wszystkie testy przechodzą, to znaczy, że nie ma w nim błędów, które mógłby wyłapać kompilator.

c) Testy jednostkowe pelnią rolę dokumentacji i listy kontrolnej. W tym pierwszym znaczeniu dobrze dokumentują kod i gwarancje, które muszą być przezeń spełnione. W tym drugim - dobrze pokazują w trakcie kodowania, co jest już zaimplementowane, a co oczekuje na implementację. Stosując się do zasady pisania testów najpierw, wiemy że nienapisany test = niezaimplementowana funkcjonalność. Dzięki temu łatwiej sprawdzić, które wymagania mamy pokryte, a które nie.

5. "Testy jednostkowe utrudniają zmiany w kodzie, bo w razie czego trzeba je przerabiać".

Z mojego doświadczenia wynika, że testy jednostkowe ułatwiają zmianę, bo duże pokrycie funkcjonalności testami daje nam dużą pewność, że nic nie popsujemy. I znowu, jeśli uznamy, że nienapisany test = niezaimplementowana funkcjonalność, to wiemy, że jeśli cały zestaw testów przejdzie po naszych zmianach, to jesteśmy bezpieczni.