niedziela, 19 maja 2013

Więcej nie znaczy wolniej cz.4 - testowanie

no to co? może teścik?

A może jeszcze jeden? I kolejny? I jeszcze dziesięć następnych? I co?

No właśnie, i co? Obrońcy rzeczywistości, w której testów nie spotkasz nawet tam, gdzie mógłbyś (a wręcz chciałbyś) się ich spodziewać, nieustannie twierdzą, że z tą aktywnością nierozerwalnie wiążą się dwa podstawowe problemy:
  • Ilość kodu, który testujemy niekiedy jest dużo większy niż ilość kodu testowanego (czyt. a można by przecież napisać tyle więcej nowej funkcjonalności)
  • Testowanie nie daje gwarancji tego, że w produkcie nie ma bugów (czyt. jesteśmy tak pewni swojej aplikacji po je przetestowaniu, jak przed)

Oczywiście są to prawdy objawione, z którymi się nie da dyskutować.

Hmm... ale zawsze można spróbować :)

poczucie bezpieczeństwa, a testy

Nie będę się kłócił ze stwierdzeniem, że linijek testów jest często więcej niż kodu właściwego. Z tym, że raczej nie uważam tego za wadę. Mnogość testów daje nam poczucie bezpieczeństwa, gdy dochodzimy do momentu, gdy trzeba coś w aplikacji zmienić, a pewnie każdy z Was wie, że niekiedy pozornie niewielkie zmiany, potrafią za sobą pociągnąć kolejne. Zarządzanie nimi bez testów, jest o tyle skomplikowane, że nigdy nie mamy pewności, czy przypadkiem po drodze nie zmieniliśmy działania jakiejś istniejącej funkcjonalności. Ryzyko to zwiększa się w przypadku, gdy pracujemy z kodem, który ostatni raz został wyświetlony na ekranie komputera za czasów, gdy jeszcze nawet nie myślałeś, że zostaniesz programistą ;)

Ok, ale przecież zmian może nie być, a w takim wypadku nie będzie potrzeby modyfikowania kodu, a co za tym idzie - potrzeba posiadania zabezpieczenia w postaci licznych testów jest nieuzasadniona.

Co do prawdopodobieństwa wystąpienia zmian, to zacytuję (po raz który?) Peter Drucker'a - Jedyną pewną rzeczą jest zmiana.
Pamiętajcie, że zmiana niekoniecznie musi oznaczać zmianę wymagań, może wynikać z chęci posiadania nowych funkcjonalności, a to bezpośrednio pociąga za sobą modyfikację kodu. Oczywiście, czasami (rzadko, bo rzadko, ale jednak) zdarza się nam dodać coś całkowicie nowego, coś, co w żaden sposób nie wpływa na aplikację. Jednak w większości przypadków rozwój oprogramowania sprowadza się, między innymi, do większych lub mniejszych operacji przeprowadzanych na żywym organizmie.

Im większe są te zmiany, tym większą ulgę odczuwamy, gdy zauważamy, że programiści przed nami zadbali o solidne pokrycie funkcjonalności odpowiednimi testami (takiego uczucia z resztą sam czasami doświadczam :)

Im większy system, im większy wpływ jego poprawne działanie ma na końcowych użytkowników, tym większe poczucie bezpieczeństwa odczuwamy, gdy wrzucamy na produkcję kolejną wersję aplikacji, która pomimo licznych zmian, nadal przechodzi wszystkie testy.

Oczywiście możemy nie testować i czekać, aż któryś z użytkowników zgłosi nam błąd, ale:
  • Nie jest to komfortowe uczucie, gdy usłyszy się od klienta, że jeszcze wczoraj wszystko działo bez problemu, a po wczorajszym updacie system wariuje. Nie wypadamy w jego oczach w takim wypadku zbyt profesjonalnie.
  • Jeżeli poprawne działanie systemu jest z jakichś powodów krytyczne, to błąd zauważony przez klienta, to nie tylko "uczucie dyskomfortu", ale często dość bolesne konsekwencje. W przypadku takich systemów, stres towarzyszący wprowadzaniem kolejnych wersji (jeżeli nie ma testów), jest czymś, co może na nas (w dłuższej perspektywie) naprawdę negatywnie wpłynąć.

nowa funkcjonalność, czy testy?

Skoro tych testów jest rzeczywiście tyle, to może naprawdę warto byłoby rozważyć, żeby zamiast ich tworzenia, zająć się czymś, co przyniesie bardziej wymierne korzyści dla firmy i klienta?
I o ile wszystko by tak prosto działało, to jeszcze byłbym skłonny się zastanawiać, jednak w życiu nigdy nic nie jest tak proste, jak na to wygląda.

W początkowych etapach tworzenia aplikacji, można by rzeczywiście zauważyć wzrost wydajność w przypadku pominięcia testów, ale takie działanie mści się bez litości w późniejszych etapach życia oprogramowania.
Każda modyfikacja, każda zmiana, nawet ta najmniejsza, trwa odpowiednio dłużej (w zależności od jej wielkości), a wynika to bezpośrednio z tego, o czym pisałem w poprzednim paragrafie - z braku poczucia bezpieczeństwa. Każdy krok wymaga większego zastanowienia, jest obarczony dużo większą niepewnością i strachem przed tym, że może być to krok niewłaściwy. Strach rodzi stres, stres zmniejsza produktywność, a zmniejszona produktywność przekłada się na szybkość implementowania nowych funkcjonalności.

Myślicie, że przesadzam z tym strachem przed zmianami i stresem tym spowodowanym?
Chciałbym.

testy nie są gwarancją

Wiele osób, które podejmują decyzje odnośnie aktywności, które będą miały miejsce w projekcie, a którzy równocześnie nie są dobrze zaznajomieni z tematem, uważa, że testy są gwarantem niezawodności aplikacji i tak też przedstawiają sytuację klientowi, gdy przekonują go, że warto poświęcić na nie trochę czasu.
W chwili, gdy klient znajdzie pierwszego buga czuje się oszukany, a osoba zarządzająca przychodzi z pretensjami do programistów i pyta się jak to się mogło stać skoro są testy? Nie jest to oczywiście zbyt zdrowa atmosfera, ale niestety zbyt często ma miejsce.

Z drugiej strony mamy managerów, którzy wiedzą o tym, że testy nie gwarantują niczego i właśnie z tego powodu decydują się na odrzucenie tego procesu.

Oczywiście testy nie są gwarancją niezawodności aplikacji i nie wynika to z braku umiejętności programistów i testerów, nie jest to efektem braku czyichkolwiek kompetencji. Po prostu przypadków użycia jest nieskończona ilość, a im wyższą (bardziej złożoną) warstwę aplikacji testujemy, tym mniej przypadków uda nam się pokryć.

Mimo to posiadanie testów zmniejsza ilość błędów w oprogramowaniu, ponieważ wiele z nich zostaje wykrytych przed wypuszczeniem produktu na rynek. Skupienie się na przypadkach granicznych, najbardziej niebezpiecznych oraz na tych, których częstotliwość występowania jest największa, pozwala nam wyeliminować wiele błędów już na etapie implementacji i daje nam pewność poprawnego działa aplikacji w takich właśnie scenariuszach użycia.

Osobiście polecam również pisać testy przed implementacją i zapoznać się z techniką Test First . Dlaczego? Z dwóch powodów. Po pierwsze, gdy już kod jest gotowy zazwyczaj nie mamy ochoty wysilać się i tworzyć wyszukanych testów, tylko chcemy jak najszybciej zacommitować nasz kod i przeciągnąć zadanie do tych na liście Done. Po drugie, pozwala to spojrzeć przez chwile na problem z innego punktu. Skupiamy się na interfejsach i działaniu, na funkcjonalności, a nie na budowie.

Ale o tym już chyba kilka razy pisałem :)

bugi, ach te bugi!

Jest jeszcze jeden istoty argument za pisaniem testów. Chodzi o tempo i jakość naprawiania bugów.

Jeżeli posiadamy testy i klient zgłosi, że któryś fragment aplikacji nie działa tak, jak powinien, to możemy napisać test case, który pokazuje, że rzeczywiście bug istnieje. Następnie możemy schodzić niżej i niżej (dopisując kolejne testy, które nie przechodzą), aż uda nam się zidentyfikować źródło problemu.
Naprawa powinna sprawić, że wszystkie testy zakończą się statusem "sukces". Jeżeli tak się nie stanie, to oznacza, że musimy szukać dalej.

Kolejną zaletą jest fakt, że naprawiając błąd nie popsujemy przy okazji obecnej i działającej poprawnie funkcjonalności. Wystarczy, że puścimy zestaw testów i będziemy wiedzieć, czy wszystko działa tak, jak przed rozpoczęciem pracy.

lepsze jest wrogiem dobrego

Nie liczy się ilość, a jakość i o tym ile i jakie testy tworzyć pisałem już w innym poście.
Jeżeli cel, jaki nam przyświeca, to stuprocentowe pokrycie kodu testami, to oznacza, że testujemy źle. Nie w tym rzecz, aby testy znalazły się przynajmniej raz w każdej linijce kodu, chodzi o to, aby przetestować to, co uważamy, że jest krytyczne dla działania aplikacji, to, co jest istotne i wrażliwe, to, co może być podatne na zmiany.

Czasami dwa, trzy testy dadzą nam więcej informacji i większe poczucie bezpieczeństwa niż zestaw dziesiątek bezużytecznych testów.