Code review: Czytelność testów

Dziś chciałbym pokazać kilka scenariuszy, które według mnie są przykładem złych testów.

O tym jak szczegółowo należy pisać testy, opiszę w następnym poście więc tym dzisiaj nie będę zajmować się.

Jednym z często spotykanych błędów jest testowanie zbyt wielu scenariuszy na raz. Test jednostkowy nie powinien wyglądać na przykład w taki sposób:

// przygotowanie danych // wykonanie operacji if(id==1) { Assert.AreEqual(1,result.Count); } else { Assert.AreEqual(2,result.Count); }

Test nie powinien zawierać żadnej logiki bo co jeśli pomylimy się? Jeśli wykonujemy różne asercje, w zależności od jakiś danych, wtedy oznacza to, że powinniśmy stworzyć drugi, osobny test. Asercje powinny być proste i jasne. Jeśli inny programista zagląda do testów i musi analizować je linia po linii, oznacza to, że  asercje są zbyt skomplikowane. Innymi słowy, żadnych instrukcji warunkowych podczas asercji.

Test powinien być napisany jak skrypt, czyli stanowić dokumentację.  Skrajnie zły przykład to:

[Test] public void Test() { base.VerifySomething(5,"Hello world"); }

W logice biznesowej często zdarza się, że metoda publiczna wywołuje bazową metodę z jakimiś parametrami, ewentualnie poprzedzając to inną prostą logiką. W testach chcemy mieć jasny skrypt zdarzeń zachodzących po kolei. Musi być wyraźnie widoczne jak dane są przygotowane, wywołane,  a na końcu zweryfikowane. Wywołując jakieś metody bazowe, tak naprawdę dla czytelnika takiego kodu, wygląda to jak czarna skrzynka, a nie dokumentacja, którą testy przecież powinny stanowić.

Nie oznacza to, że musimy duplikować kod we wszystkich testach, które wykorzystują podobne kroki. To naturalne jest, że duża część kodu może być taka sama dla różnych testów. W takich przypadkach, zamiast implementacji jednej, dużej metody, która wykonuje wszystkie kroki, lepie opakować w metody wyłącznie pojedyncze kroki. Podobnie z asercjami, można w końcu skomplikowane asercje  zaimplementować za pomocą metod rozszerzających. Nie powinniśmy jednak kopiować wszystkich asercji do osobnej metody, nawet jak są wykorzystywane w różnych testach. Tak jak podkreślałem wielokrotnie już, test powinien stanowić skrypt opisujący testowany scenariusz.

Nie ma łatwej reguły co powinno trafiać do osobnych metod, a co powinno być bezpośrednio w metodzie testującej. Celem jest stworzenie przejrzystego testu, który można przeczytać i zrozumieć bez skakania do różnych metod w klasie\projekcie. Bardzo kuszące jest zlikwidowanie zduplikowanego kodu (wykorzystywanego w różnych testach) poprzez ekstrakcje go do innej metody, ale trzeba to robić z rozsądkiem. Nie chcemy mieć w końcu w każdym teście czarnych skrzynek w postaci metod typu “PrepareData” czy “InitializeScenarioSteps”. Czasami nawet lepiej pozostawić pewien kod zduplikowany (oczywiście w granicach rozsądku) niż na siłę wszystko automatyzować i wydzielać.

Leave a Reply

Your email address will not be published.