Thursday, 18 October 2012

Opowiastka

Dziś będzie opowiastka, więc będzie po polsku, bo tak łatwiej.

Z okazji zmiany numeru telefonu na fejsbuczku, portal ten zaskoczył mnie czymś, co chyba trzeba potraktować jako rodzaj troski...

Prawda, że to miłe? Ktoś nade mną czuwa!

No dobrze, ale co z tego? No w sumie nic... bo w realnym życiu, a przede wszystkim w realnej pracy, czuwać powinniśmy sami nad sobą. Jak?! Ano oczywiście testami...

Wczoraj, w sumie to całkiem świeżo po przeczytaniu powyżej przytoczonego maila, kumpel w pracy zapytał mnie o kod, w którym jakiś czas temu coś zmieniałem... Od tego czasu minęły ponad 2 tygodnie, a ja zdążyłem zwiedzić 2 projekty, więc mój bufor zdążył się wyczyścić i zapełnić czymś zupełnie innym. Owe "coś" to były zmiany nietrywialne - w sumie bardziej przepisanie niemal od zera części funkcjonalności, niż refaktoring. Pytanie sprowadzało się mniej więcej do: czemu to nie działa?! No i miałem problem... Kod, którzy przerabiałem przez ponad 2 dni, okazał się wadliwy. Można by te zmiany zrevertować, gdyby nie to, że na ich bazie szedł cały późniejszy development i w międzyczasie doszło już pewnie kilka lub kilkanaście commitów kolegów z teamu. Więc najprostszą i w sumie jedyną opcją było wytropienie błędu.

Testów było mało, ale wszystkie przechodziły... Na co więc komu takie testy? teoretycznie psu na budę... ale tylko teoretycznie. Przypomniałem sobie, że ten mój refaktoring też był otestowany, bo inaczej bym się za to w ogóle nie zabierał, bo tyczyło się to problemu, z którego wiedzę domenową mam nadal bardzo nikła, więc moja praca z tym kodem wygląda następująco: ok, kompiluje się, to fajnie... ale czy działa jak należy? - nie mam pojęcia! 

Funkcjonalność, którą miałem przebudować, polegała na tym, że dla danego wejścia do metody, powinno się uzyskać ściśle określone wyjście, które wynikało z wiedzy domenowej. Pierwotnie była to zwykła metoda prywatna, którą trzeba było gruntownie przebudować i przenieść do commonsów, ale efekt miał pozostać taki sam. Zacząłem więc od testu, który mi wypisze (tak, nie użyłem loggera, lecz System.outy!) wszystkie możliwe wyniki działania tej metody. Parę sekund po tym miałem już listę spodziewanych rezultatów, którą mogłem iterować i wewnątrz assertEquals'ów, których liczba rosła wraz z posuwaniem się naprzód wspomnianego przerabiania kodu. (Nawiasem mówiąc, myślę, że jest to podejście, które mógłbym polecić każdemu, kto ma zmieniać coś, o czym ma dość nikłe pojęcie - najpierw solidnie przetestować  pierwotne działanie a dopiero potem poprawiać/psuć kod początkowy.) Na sam koniec kod z commonsów działał dokładnie tak jak ten pierwotny, ale był napisany znacznie lepiej (o czym przekonałem się dziś, kiedy musiałem dołożyć nową funkcjonalność).

Wszystko ładnie pięknie - były testy, kod udawał, że działał. Stało się tak, dlatego, że testowałem kod z commonsów w oderwaniu od kodu, który go używał. Musiałbym powiększyć zakres moich początkowych testów, a na to byłem zbyt leniwy. Okazało się, że zrobiłem coś w rodzaju literówki - w kliencie użyłem nie tej metody, o której myślałem ;) Ale "literówki" każdemu mogą się zdarzyć (zapraszam na ponowne rzucenie okiem na mail od fejsbuczka).

Dobre testy bronią nawet przed literówkami! (można np. testować zamierzony komunikat błędu z exceptiona  aby uniknąć literówek;) ale wróćmy do rzeczy poważnych) A nawet jeśli nie, to stanowią dokumentację dla kodu, pokazują czy działa i jak działa, dzięki czemu mogą bardzo wydatnie przyczynić się do przyśpieszenia momentu w którym wykryjemy i naprawimy błąd.

Skoro mowa jest o testach, to mogę polecić wpis mojego znajomego, Mariusza Sieraczkiewicza Wielkie kłamstwo o czystym kodzie i testach jednostkowych, a nawet bardziej komentarz, którzy pojawił się pod nim:


Motywacja i chęć doskonalenia swoich umiejętności to jedno, ale same chęci nie wystarczą. Zdecydowana większość osób, które nie uznają testów jednostkowych - tak na prawdę nie wie jak pisać testy i kod, który da się testować. Mnie to nie dziwi, bo wg mnie trudno jest znaleźć dobre materiały na ten temat. Dodatkowo sama teoria nie wystarczy, potrzebna jest praktyka. Tutaj jest problem, bo jeśli zapragniemy zdobytą wiedzę teoretyczną wdrożyć do projektu, nad którym pracujemy w pracy - to tak jakbyśmy uczyli się jeździć na nartach zaczynając od zjazdu ze ścianki. Katastrofa murowana. Trzeba zaczynać od rzeczy małych i prostych. I powolutku poznawać o co w tym wszystkim chodzi.

To wyżej tłumaczy dlaczego tak mało osób używa technik TDD. Jest jednak jeszcze jeden problem: bardzo łatwo jest popełnić błędy podczas poznawania tajników metodyki TDD i zostać jej zagorzałym przeciwnikiem.

Na to wszystko nakłada się jeszcze kolejny aspekt - testy jednostkowe to tylko mały i stosunkowo prosty kawałek układanki. Schody się zaczynają kiedy przychodzi nam ogarnąć testami szerszy zakres: mówię o testach funkcjonalnych i integracyjnych. Tutaj trzeba już czegoś więcej niż poznania kilku prostych zasad, jak ma to miejsce w przypadku testów jednostkowych.

Podsumowując: pisanie o tym, że trzeba pisać testy i że te testy to jest podstawa itp. rzeczy - to nie wystarczy. Tutaj potrzebna jest edukacja "od podstaw", jak czynią to np.:
  • Steve Freman i Nat Pyrce w swojej doskonałej książce (polecam!),
  • Misko Hevery na swoim doskonałym blogu (polecam!),
  • Gregory Moeck w swojej doskonałej prezentacji o tym dlaczego nie pojmujecie mock'ów (polecam!),
  • i inni... (szukajcie, a znajdziecie).

Zadałem sobie nieco trudu i rozszyfrowałem wszystko o czym napisał Witold Szczerba ;)
  1. Misko Hevery i jego blog 
  2. Gregory Moeck i jego rzeczona prezentacja
  3. i inni ;)

powodzenia!

2 comments:

  1. Również polecam "Growing Object-Oriented Software Guided by Tests" - jedna z nielicznych książek o testowaniu, gdzie piszemy semi-rzeczywistą aplikację a nie "klasę Kalkulator implementującą dodawanie".

    ReplyDelete
    Replies
    1. Co ciekawe, Gregory Moeck w jego prezentacji o mockach (którą również podlinkowałem) także wielokrotnie poleca tę książkę, więc z pewnością jest warta lektury.

      Delete