blog.testowka.pl

Dlaczego automatyzujemy testy?

opublikowany przez 21, cze, 2012, w kategoriach Agile, Automatyzacja, CI, Programowanie, Testowanie, XP

Mamy różne rodzaje testów. Jeśli chodzi o testerów to najczęściej zajmują się oni automatyzacją testów funkcjonalnych GUI czy też testami typu end-to-end.

Warto się zastanowić po co tworzymy takie testy automatyczne? Jeśli robimy to wyłącznie by przyspieszyć testowanie, to nie jest to najlepsza odpowiedź. Celem tworzenia takich testów powinno być poprawianie jakości – może nie bezpośrednio ale ogólnie testy automatyczne powinny przyczyniać się do poprawy jakości. Robię to poprzez dostarczanie szybkiej - dużo szybszej niż tester manualny informacji zwrotnej na temat działania produktu, a przede wszystkim na temat tego czy programista wprowadzając zmiany niczego nie zepsuł. Jest to niezwykle istotne zwłaszcza, gdy mamy do czynienia z systemami typu legacy – kaszana bez wyraźnej, przejrzystej architektury i testów jednostkowych. Systemy tego typu maja to do siebie że bardo trudne, o ile wręcz niemożliwe jest przetestowanie ich przy użyciu testów jednostkowych. Tworzenie unit testów do już istniejącego kodu, napisanego przed testami jest bardzo czasochłonne (swoją drogą to jeden z powodów dla którego niektórzy mówią, że pisanie testów jest strasznie kosztowne i czasochłonne). Kod który nigdy nie był pisany z myślą o testach jednostkowych, które będą go testowały często jest po prostu nietestowalny.

By móc poprawiać jakość naszego produktu musimy być w stanie poprawiać kod i jego jakość. Niestety bez szybkiej informacji na temat tego czy kod działa  - na przykład w postaci unit testów nie mamy nigdy pewności czy nasze zmiany niczego nie popsują, a co za tym idzie brakuje nam  odwagi by cokolwiek zmieniać i poprawiać. Pętla się zamyka – nie możemy zmienić nie testowalnego kodu tak by był testowalny bez obawy o to, ze czegoś nie popsujemy. Błędne koło się zamyka a dług technologiczny ciągle rośnie.

Jednym ze sposobów jest wdrożenie testów typu end-to-end które traktują testowany system jak „black box” lub „gray box”, do którego mają dostęp tylko w odpowiednich miejscach. Takie testy mają niestety kilka wad – na przykład są wolne i trudne w utrzymaniu.

Powyższe problemy to jeden z wielu powodów stosowania w projektach tzw. piramidy testów, która zakłada, że będziemy tworzyć jak najwięcej testów jednostkowych, trochę mniej testów funkcjonalnych i akceptacyjnych, a testów typu end-to-end nie będziemy tworzyć wcale, lub będą to tylko skrajne przypadki.

Takie podejście sprawdza się idealnie, rozpoczynamy projekt od zera i od samego początku postępujemy według wyżej wymienionych zasad. Niestety rzeczywistość jest inna. Na początku projektów informatycznych mało kto przejmuje się automatyzacja w ogóle, o testach jednostkowych i TDD już nawet nie wspomnę. Testy automatyczne wdraża się przeważnie dopiero, gdy zaczyna się już robić na prawdę niedobrze, a dług technologiczny daje o sobie znać na każdym kroku. W ten sposób powstają właśnie systemy typu legacy.

Gdy kilka miesięcy temu na konferencji 33rd degree podczas jednej z prezentacji Uncle Bob powiedział coś w stylu: „Nie piszcie testów end-to-end – one są złe”, a ja zobaczyłem bezmyślnie przytakujące głowy setek programistów siedzących na sali pomyślałem sobie: „Cholera – ostatnie 2 lata prowadzonej przez nas indoktrynacji w dziedzinie automatyzacji testów w naszym kraju właśnie trafił szlag…”. Miałem o to spory żal do Bob’a, który zresztą na niego wylałem w postaci bardzo długiej dyskusji na temat legacy code etc. Bob oczywiście na myśli miał projekty typu green-field, gdzie zaczynamy od zera. Oczywiście bardzo dobrze, że skorzystał ze swojego autorytetu i może przynajmniej kilka osób do tego przekonał, dzięki czemu w przyszłości będzie znacznie mniej systemów przepełnionych legacy code. Ale co z projektami, które już istnieją i które nadal trzeba rozwijać? To właśnie w takich projektach pracuje większość testerów (domyślcie się dlaczego).

Łatwo znaleźć rozwiązanie dla systemów już istniejących, gdy uświadomimy sobie, że celem testowania nie jest tylko testowanie samo w sobie ale też poprawa jakości. Podstawowym celem wdrożenia testów automatycznych jest zapewnienie możliwości wprowadzania bezpiecznych zmian – podobnie jak celem testów regresyjnych (które przypadkiem powstają przy wdrożeniu automatyzacji) jest dostarczenie informacji zwrotnej na temat tego, czy nasze zmiany nie wprowadziły błędów w istniejącej już funkcjonalności. Aby móc cokolwiek zmieniać tworzymy takie testy które dadzą nam przynajmniej ogólna informację na temat tego czy funkcjonalność która zmieniamy nie uległa popsuciu z punktu widzenia użytkownika.

W skrócie tworzymy odwróconą piramidę testów – z dużą ilością testów end-to-end i funkcjonalnych. Dzięki temu możemy zmieniać kod aplikacji i poprawiać jego jakość (refaktoryzować, zmieniać architekturę etc.) sprawiając, że staje się on testowalny także na niższych poziomach. Za każdą zmianą idzie dopisywanie nowych testów jednostkowych (a nawet to zmiany sterowane są pisaniem testów jednostkowych jak w TDD). Po pewnym, czasie mamy testowalny system z przejrzystą architekturą, w której każdą warstwę możemy przetestować oddzielnie, a GUI i logika w nim ograniczone są do minimum. To jest dobry moment by zacząć pozbywać się naszych testów typu end-to-end. Tak! Właśnie tak! Cały czas tworzyliśmy testy typu end-to-end z myślą o tym, że za chwilę je usuniemy. Największym kosztem podczas utrzymywania testów są duplikacje – więc jeśli tworzymy testy jednostkowe, które w przybliżeniu testują to samo co nasze testy end-to-end to oczywistym staje się potrzeba usunięcia tych, które są bardziej kosztowne i mniej wartościowe – end-to-end. W ten sposób stopniowo obracamy naszą piramidę testów i zaczyna ona powoli wyglądać tak jak powinna.

Celem całej naszej pracy nie jest zwiększenie pokrycia testami – co w podanym przykładzie mogło by doprowadzić do pokrycia nawet powyżej 100% (oczywiście zależy jak liczonego) – pytanie czy taka metryka cokolwiek nam mówi poza tym, że straciliśmy dużo czasu? Prawdziwym celem jest poprawa jakości, jakości na najniższym poziomie, zapewnienie poczucia bezpieczeństwa podczas wprowadzania zmian, wydzielenie odpowiednich modułów i domen w naszej aplikacji, odseparowanie modelu o danych, logiki od interfejsu użytkownika, itd.

Powyżej opisałem jeden (jak dotąd z pośród sprawdzonych przeze mnie w praktyce, jedyny działający) z kilku sposobów radzenia sobie z długiem technologicznym.


3 Comments for this entry

  • Tomek Kaczanowski

    Wiktor,

    jaki test jednostkowy zapewni mnie że user może bezpiecznie klikać po frontendzie? Może jednak nie pozbywajmy sie tych end-to-endów tak po prostu… ?

  • akawka

    Dokładnie zgadzam się z Tomkiem. Czy można powiedzieć, że test jednostkowy jest w przybliżeniu zduplikowany przez test end-to-end? Nastawione są przecież raczej na testowanie w innym, szerszym kontekście. W założeniu powinny mieć znacznie więcej interakcji z systemem, testować konkretne ścieżki, „większy biznes”.
    Kolejna sprawa, to dobrze napisane testy end-to-end wcale nie muszą być trudne w utrzymaniu. Owszem, zazwyczaj wymagają więcej zmian, poprawek ale przy sensownej architekturze nie stanowi to wielkiego problemu.
    Z drugiej strony mocno zaskoczyła mnie przytoczona wypowiedź Uncle Boba, mi także jest ciężko pogodzić się z jego punktem widzenia. Chętnie bym się dowiedział czym argumentował swoją wypowiedź, bo wg mnie nawet w projektach od zera testy end-to-end są przydatne i nie uważam ich za stratę czasu. Czy w takim razie Uncle Bob uważa, że wystarczą testy niższego poziomu?

  • edward

    Testy end-to-end w podstawowej ilości są niezbędne, gdy software budujemy z wykorzystaniem systemów zewnętrznych, framework’ów etc., których nie jesteśmy autorami i których specyfikacja jest zbyt szeroka i szczegółowa, by ją w pełni zamockować. Przykład z autopsji – nakładka na system zadania quartz, jednostkowo przetestowana z wykorzystaniem mocków, efekt – wszystkie testy na zielono. Po wrzuceniu na serwer aplikacyjny problem z: wadliwą strukturą bazy danych, brakującymi bibilotekami runtime,błędami przy wywołaniu API quartz. Rezultat – fatalny, nic nie dział. Gdybym ufał jedynie testowi jednostkowemu, daleko bym nie zajechał.

1 Trackback or Pingback for this entry

Skomentuj