Archive for Grudzień, 2010

Automatyczne testy w zmieniającym się środowisku

W poprzednim poście zwróciłem uwagę na pewne problemy, związane z pisaniem automatycznych testów, które wynikły w ramach konkretnego przypadku w naszym projekcie. Dla przypomnienia, sednem problemu były zmiany w kodzie wywołane refaktoryzacją i zmianami wymagań. Jako, że ostatnio skoncentrowałem się po części na refaktoryzacji, teraz chciałbym poruszyć temat zmieniających się wymagań.

Od samego początku mojej pracy jako programista napotykam się na projekty, w których bardzo dynamicznie zmieniają się wymagania. I tak się dzieje bez względu na to, czy zostanie przygotowana wcześniej szczegółowa dokumentacja potrzeb klienta, czy wymagania zostają definiowane ad-hoc. Początkowo, podobnie jak zapewne was wszystkich, strasznie mnie to wkurzało, ale z czasem zrozumiałem, że tak działa w dzisiejszych czasach biznes i w związku z tym należy się do tego dostosować. Dlatego też uważam metodyki zwinne za jedno z lepszych narzędzi, które pozwala nam programistom odnaleźć się w tych realiach.

Zgodnie z manifestem Agile, metodyki zwinne koncentrują się na odpowiednim reagowaniu na zmiany. Stąd też iteracyjne podejście jest jednym z filarów tych metodyk. Jednak samo podejście iteracyjne nie wystarczy. Jedną z głównych wad metodyki Scrum – jednej z najbardziej znanych metodyk zwinnych, okazał się brak wskazówek dotyczących realizowania projektów IT od strony technicznej (programowania). Zastosowanie miękkich technik zarządzania zawartych w Scrumie, prowadzi często do tego, że wydajność zespołu spada w późniejszej fazie projektu dlatego, że wytwarzane oprogramowanie staje się z każdą iteracją coraz to trudniejsze w zarządzaniu i rozwijaniu. Więcej na temat tych problemów można znaleźć w poście Jamesa Shore’a.

Jedną z najczęściej wspominanych praktyk, która pozwoli na zminimalizowanie tego typu problemów jest automatyzacja testów – najlepiej wprowadzając w oparciu o test driver development(TDD). Dlatego też wiele osób dzisiaj mówi, że TDD to jeden z filarów metodyk zwinnych. Rzecz w tym, że tak naprawdę zawsze praktyki takie jak TDD były ważnym elementem metodyk zwinnych. Przykładem na to może być metodyka zwinna Extreme Programming, w której skład wchodzi TDD. Nawet sam Ken Shwaber (twórca Scruma) mówi w książce Agile Project Management with Scrum o automatyzacji testów i innych pokrewnych technikach, jako narzędziach dopełniających scruma. Podobną tematykę porusza również Mike Cohn w książce Succeeding with Agile.

W związku z tym można dojść do wniosku, że TDD jest i zawsze był ważnym elementem metodyk zwinnych. A to z kolei znaczy, że unikanie testów automatycznych, argumentowane zmianami wymagań klienta jest nieuzasadniona. Moim zdaniem problem tkwi w tym, że w większości przypadkach programiści rozpoczynając przygodę z automatyzacją testów borykają się z problemem zarządzania testami, które szybko zniechęcają do tego podejścia. Przed omówieniem tego problemu w szerszym kontekście, chciałbym zwrócić uwagę na to, dlaczego tak ważne jest stosowanie testów automatycznych w projektach, w których często zmieniają się wymagania.

W porównaniu do wprowadzania nowych funkcjonalności w systemie, zmiana istniejącej funkcjonalności wiąże się najczęściej z większym ryzykiem. Dzieje się tak dlatego, że w większości przypadków zmiany istniejącej funkcjonalności nie zmieniają w stu procentach logiki już istniejącej funkcjonalności, lecz tylko jej część. To z kolei prowadzi do tego, że zmieniając tą część funkcjonalności możemy w szybki i niezauważony sposób wprowadzić nowe błędy w tej części, która nie powinna ulec zmianie. Nie mając w zapasie testów automatycznych nie jesteśmy w stanie szybki i wygodny sposób wychwycić tego rodzaju błędów. Warto zwrócić również uwagę na to, że wprowadzając zmiany w funkcjonalności powinniśmy zawsze się starać poprawiać już istniejący kod poprzez refaktoryzację. Zmiany funkcjonalności często są związane ze zmianą logiki biznesowej, a to z kolei znaczy, że kod, który wcześniej idealnie abstrahował logikę biznesową danej funkcjonalności nie musi koniecznie się sprawdzać w momencie, kiedy wprowadzimy nowe zmiany. Tego typu sytuacje często prowadzą do refaktoryzacji zwanej „breakthrough” opisanej szczegółowo w książce Domain Driven Design Erica Evansa. Refaktoryzacje te są bardzo trudne w realizacji bez odpowiedniego pokrycia kodu testami automatycznymi. Jeżeli z kolei pominiemy tego typu zmiany, szybko będziemy zmuszeni do zwrócenia tzw. długu technicznego, poprzez zmniejszenie naszej wydajności w kolejnych iteracjach. Więc jak widać koło się szybko zamyka.

Zapewne sobie myślicie, że fajnie mi się mówi, ale w praktyce z tym już nie jest tak łatwo. Zgadza się. Tak jak już wcześniej wspominałem, utrzymywanie testów automatyczny też kosztuje i w wielu przypadkach źle napisany testy będą dla nas uciążliwe i będą nas spowalniać prawie że na każdym kroku w trakcie wprowadzania zmian w istniejącej funkcjonalności. Stąd też, często można spotkać się z sytuacją, gdzie w późniejszej fazie projektu wcześniej pisane testy automatyczne zostają ignorowane. Istnieje wiele porad dotyczących poprawy jakości testów automatycznych, przede wszystkim testów jednostkowych, które trudno było by opisać w ramach jednego wpisu. Mimo tego chciał bym zwrócić uwagę na parę ważnych praktyk eliminujących przedstawiony wcześniej problem, którym powinniście się przyjrzeć bliżej.

Pisząc produkcyjny kod powinniśmy się starać pisać metody, które są krótkie i przejrzyste, dlatego, że są one łatwiejsze do zrozumienia i zarządzania. Stosowanie tej zasady również jest konieczne w przypadku automatycznych testów, gdyż zapobiega ona powstawaniu nieczytelnych testów. Nieczytelne testy mają to do siebie, że najszybciej je ignorujemy w przypadku, gdy dane testy nie wykonują się poprawnie po wprowadzeniu zmian. Tworzenie krótkich i zrozumiałych testów wiele pomaga, ale mimo tego pojawiają się często problemy związany z tym że, aby zrozumieć dany test musimy przejrzeć całe ciało testu. Temu można w łatwy sposób zapobiec, poprzez stosowanie odpowiednich nazw testów. Praktyka ta też się sprawdza w metodach produkcyjnego kodu, przy czym w wielu przypadkach testy będą opisane dłuższą i bardziej wyczerpującą nazwą. Poniżej znajdziecie przykład testu jednostkowego NUnit z projektu, w którym uczestniczę.

[Test]
public void Registering_twice_the_same_publisher_for_the_same_event_will_rise_an_error()
{
    var sut = new EventBroker();
    var publisher = new PublisherMock();           
    var subject = "test";
    var eventName = "MyEvent";          
    sut.RegisterPublisher(subject, publisher, eventName);

     Assert.Throws(typeof(InvalidOperationException), () => sut.RegisterPublisher(subject, publisher, eventName));
}

Należy zwrócić uwagę, że w powyższym teście istnieje tylko jedno zapewnienie(Assert w NUnicie), które sprawdza wynik testu. Stosując tego typu zasadę zapewniamy, że dany test ma tylko jedno i jedyne znaczenie, które powinno być opisane w nazwie testu. W wielu przypadkach spotykam się z testami, które w powyżej przedstawionym przypadku, sprawdzały by dodatkowo wynik pierwszego wywołania metody RegisterPublisher wprowadzając dodatkowe zapewnienie. To jest moim zdaniem kolejny z podstawowych błędów, który prowadzi do wcześniej opisanych problemów z nieczytelnymi testami.

Stosując wyżej przedstawione zasady szybko zauważycie, że każdy test ma odpowiedni kontekst, w ramach którego weryfikujemy wynik końcowy. Test w danym kontekście powinien jedynie ulec zmianie w momencie gdy zostaną wprowadzone zmiany wymagań dotyczące danego kontekstu. Za tym z kolei idzie to, że nie jesteśmy zmuszeni, przy zmianie części funkcjonalności zmieniać wszystkie testy, lecz tylko te, które są w kontekście zmian funkcjonalności. Dla osób, które spotkały się z metodyką behaviour driven development(BDD) i pisaniem testów w oparciu o wzorzec Context/Specification, zapewne to podejście jest znajome. Z kolei dla tych osób, które nie miały wcześniej do czynienia z tym wzorcem, polecam zapoznanie się z narzędziem Machine Specification, znanym szerzej jako MSpec, które w prosty sposób pozwala na pisanie testów w oparciu o ten wzorzec.

Oczywiście to nie wszystko i jak już wcześniej wspominałem, przekazanie całej wiedzy wymagało by prawdopodobnie spisania książki. Mimo to mam nadzieję, że byłem wam w stanie przedstawić jak ważne jest stosowanie testów automatycznych w przypadku wielu zmian wymagań biznesowych i jak ważna w tym jest jakość testów.

Grudzień 21, 2010 at 1:43 pm 1 komentarz

Dlaczego unikamy pisania automatycznych testów?

źródło: http://vfxstudios.net/portfolio/3d-character-design-rae/3d-character-rae.html

Ostatnio odbyłem bardzo ciekawą rozmowę z kolegą z pracy, która dotyczyła automatyzacji testów. Powodem tej dyskusji była duża liczba błędów, w jednym z modułów naszej aplikacji, za którą kolega był odpowiedzialny.Błędy te powstawały głównie ze względu na duże zmiany w istniejącym już kodzie wymuszone zmianą wymagań i refaktoryzacją. Osoby, które mnie znają zapewne zgadną jakie było moje pierwsze pytanie. Oczywiście było to:

A gdzie się podziały wszystkie testy w tym module?

Okazało, się, że jako jedyny z dostępnych modułów w naszej aplikacji nie posiadał on w ogóle żadnych automatycznych testów. To oczywiście nie znaczy, że w pozostałych modułach nie występują błędy, ale jest ich zdecydowanie mniej.

Kolega tłumacząc się odpowiedział, że nie miał po prostu na to czasu. Hmm, nic nowego, już chyba po raz setny słyszę tego typu odpowiedź. Kontynuując zapytałem się czy jego zdaniem testy automatyczne mają sens. I tutaj się ucieszyłem, bo kolega odpowiedział, że tak. Więc idąc za ciosem wdałem się w dyskusje z nim pytając, dlaczego ich nie pisze, jeżeli jest przekonany, że mają sens. Z dyskusji tej wynikły następujące wnioski, z którymi chciałbym się z wami podzielić.

Większość programistów za główną wadę pisania automatycznych testów – przede wszystkim jednostkowych, uważa potrzebę poświęcenie dodatkowego czasu na napisanie tych testów. Z drugiej strony wydaje mi się że każdy kto przynajmniej parę razy w życiu refaktoryzował istniejący kod, nie mając w zapasie testów potwierdzających poprawność kodu, chyba przyzna mi rację, że tego typu refaktoryzacja to po prostu katorga. Uważam, że w większości przypadków brak automatycznych testów jest jednym z głównych powodów, dlaczego rezygnujemy z poprawiania działającego ale nie czytelnego kodu. Patrząc na to łatwo można dojść do wniosku, że jeżeli chcemy utrzymywać porządek w naszym kodzie, to wymagane jest od nas utrzymywanie testów automatycznych, które w każdej chwili możemy uruchomić po to, by sprawdzić czy wszystko nadal działa poprawnie.

W wielu przypadkach programiści piszą testy dopiero po napisaniu funkcjonalności, która ma być przetestowana. Przyznam się szczerze, że czasami mi się to również zdarza, rzadko ale się zdarza. W przypadku gdy dopisuje test już po zaimplementowaniu funkcjonalności, czuję pewną niechęć i brak potrzeby pisania tego testu. Dzieje się tak głównie dlatego, że jestem zbyt pewny siebie (a może zbyt leniwy) i uważam, że napisany raz przeze mnie kod jest już poprawny. W wielu przypadkach jednak się mylę. W najgorszych przypadkach dochodzi do tego, że wkrada się błąd w kodzie dlatego, że pominąłem test. Jako, że życie mnie już pokarało parę razy za tego typu podejście, staram się pisać testy zawsze przed implementacją funkcjonalności. Poza tym pisanie testów po to dla mnie i dla wielu programistów jest jednym z najbardziej wkurzających zajęć. Jeżeli dodatkowo „ciśnie nas termin” w większości przypadkach zrezygnujemy z pisania testów po implementacji, dlatego, że traktujemy to jako marnotrawienie naszego cennego czasu. Jeżeli się zgadzacie z tym stwierdzeniem to zastanówcie się czy nie jest to dla was tylko dodatkowa wymówka na to, by się wywinąć z pisania testów?

Jeżeli rzeczywiście macie takie odczucie, to mam dla was małą oczywistą radę. Zaczynajcie pisanie kodu od napisania testu. Oczywiście to zajmie wam dodatkowy czas i początkowo trudno wam będzie wyszacować czas, który będzie potrzebny na realizację zadania. Z drugiej strony pisząc test przed napisanie funkcjonalności nie będziecie tak szybko rezygnować z pisania testów automatycznych. Za tym idzie z kolei to, że szybciej poznacie rzeczywistą wartość testów automatycznych, a w przyszłości będziecie też wam łatwiej wyszacować czas potrzebny na napisanie funkcjonalności wraz z testami.

Dla osób, które nie wiedzą jak zacząć z TDD polecam listę webcastów opublikowaną przez Łukasza. Przede wszystkim polecam webcasty z DNR TV w wykonaniu Jean Paul Boodhoo – sam z nich niejednokrotnie korzystałem, aby poznać TDD.

Grudzień 5, 2010 at 2:44 pm 4 uwag


Aktualnie czytam


Follow

Otrzymuj każdy nowy wpis na swoją skrzynkę e-mail.