środa, 17 marca 2010

Testy end-to-end w TDD


Steve Freeman i Nat Pryce napisali książkę, która wywołuje dużo poruszenia w światku wytwarzania oprogramowania. Growing Object-Oriented Software Guided by Tests jest oceniana jako unikalna - dotychczas takiej książki o TDD nie było. Moim zdaniem w całej dziedzinie tworzenia oprogramowania rzadko spotyka się książki tak praktyczne, z dużą ilością kodu i świetnym przedstawieniem toku myślenia autorów.

Lektura tej książki skłoniła też Jacka Laskowskiego do napisania (jak na razie) dwóch postów. Chciałbym tutaj skomentować jego wypowiedź z pierwszego z nich. Najpierw krótko sparafrazuję myśli Jacka, do których chciałbym się odnieść:

Jacek pisze, że dotychczas zakładał, że w TDD chodzi o testy jednostkowe, czyli testujące poszczególne klasy w izolacji.
Tymczasem zaskoczyło go proponowane w książce podejście, w którym rozpoczyna się tworzenie aplikacji od napisania przekrojowego testu dotyczącego jednej prostej funkcjonalności. Chodzi o wyspecyfikowanie zachowania aplikacji z uwzględnieniem jej wszystkich warstw, od interfejsu użytkownika do połączeń z zewnętrznymi systemami, zanim jeszcze powstała architektura aplikacji. Jacek zastanawia się, który zespół wytrzyma tworzenie takiego testu, kiedy nic jeszcze nie ma? Wymaga to dużej determinacji i zdyscyplinowania - rzadko spotykanych zalet zespołów programistycznych.
Co daje nam stosowanie podejścia Test Driven? Jedną z zalet jest szybkie pojawienie się informacji zwrotnych co do poprawności stworzonego przeze mnie kodu. Kiedy piszę test jednostkowy, uruchamiam go i widzę czerwony pasek jUnita, wiem, że mam coś do zrobienia. Po napisaniu implementacji zielony pasek mówi mi, że skończyłem, że ten kawałek kodu działa, i mogę zająć się refaktoryzacją. Dzięki temu pracuję szybko, bo nie muszę uruchamiać aplikacji po każdej zmianie (lub debuggera jeśli po wprowadzeniu 10 zmian uruchamiam aplikację i nie działa poprawnie).

Patrząc od strony tego doświadczenia, to jest szybkości i pewności tworzenia oprogramowania, dość naturalnym wydaje się chęć, aby tak pracować jak najczęściej. Powiedzmy, że stworzyłem test-first warstwę dostępu do danych dla pewnej funkcjonalności i tak samo warstwę domenową. Teraz chciałbym wiedzieć, czy będą poprawnie współpracowały. Piszę zatem test, który będzie testem integracyjnym, idącym przez te dwie warstwy. Wymusi on na mnie napisanie kodu łączącego ze sobą obiekty z tych dwóch warstw w odpowiedni sposób (wiring).

Idąc dalej: powiedzmy, że właśnie rozpoczynam tworzenie projektu i czeka mnie stworzenie jego początkowej architektury. Mając doświadczenie szybkiej i automatycznej informacji zwrotnej, uzyskiwanej dzięki testom pisanym przed implementacją, mogę chcieć uzyskać taki feedback także i w tym przypadku. W tym celu tworzę właśnie test czegoś, co twórcy książki nazwali The Walking Skeleton, czyli szkieletu wszystkich warstw aplikacji. Uruchamiam go i szybko mam informację zwrotną, czy moja infrastruktura jest już gotowa, czy może potrzebuję jeszcze dodatkowego kodu lub konfiguracji, aby aplikacja zaczęła działać.

Powyższe rozumowanie mam nadzieję pokazuje, dlaczego mógłbym chcieć tworzyć aplikację w sposób opisany przez autorów książki. Tym niemniej, nie znaczy to, że jest to jedyna możliwość w zakresie szeroko rozumianego nurtu TDD. Możliwości tworzenia aplikacji w sposób test-driven jest wiele. Programiści często wypracowują swój styl, a różnice mogą dotyczyć różnych aspektów, takich jak stosowanie testów akceptacyjnych, integracyjnych, używanie mocków, podejście do testowania interfejsu użytkownika.

Na przykład autorzy wzmiankowanej książki, Steve Freeman i Nat Pryce, stosują podejście do TDD, które można określić jako "Mockist approach", tzn. że mocki są podstawowym narzędziem w tworzeniu testów. Zainteresowanych dokładnym omówieniem różnic pomiędzy tym podejściem a podejściem klasycznym odsyłam do artykułu Martina Fowlera.

Co do pisania testów przekrojowych (end-to-end), to ja nie jestem ich zwolennikiem i zdecydowanie staram się ich unikać. Moim zdaniem testy jednostkowe plus trochę testów integracyjnych daje wystarczająco dużą pewność co do poprawności działania aplikacji. Wadą testów przekrojowych jest przede wszystkim ich powolność. Wyobrażam sobie, że mógłbym ich użyć do stworzenia architektury i być może zainspirowany książką spółki Freeman&Pryce spróbuję, jak taki sposób pracy "smakuje". Dotychczas używałem ich rzadko i raczej jako testy regresji, czy smoke testy - zgrubne sprawdzenie, czy nie popsuliśmy aplikacji.
Jeśli już używamy testów end-to-end, trzeba pamiętać o zachowaniu proporcji - zgodnie z piramidą testów tych przekrojowych powinno być jak najmniej.

5 komentarzy:

  1. Jestem w niebie :) Krzysiek wspomniał o moich doświadczeniach (albo ich braku) z TDD! Tyle, że założył Powiedzmy, że stworzyłem test-first warstwę dostępu do danych dla pewnej funkcjonalności i tak samo warstwę domenową. Teraz chciałbym wiedzieć, czy będą poprawnie współpracowały. Piszę zatem test, który będzie testem integracyjnym, idącym przez te dwie warstwy.. W tej książce polecają jednak najpierw testy przekrojowe, później dopiero jednostkowe, a w ramach ugłaskiwania kompilatora pojawia się kod źródłowy samej aplikacji. Nie ma wiele mowy o testach integracyjnych i pisaniu czegokolwiek innego niż przekrojowych najpierw.

    OdpowiedzUsuń
  2. W tej książce polecają jednak najpierw testy przekrojowe, później dopiero jednostkowe, a w ramach ugłaskiwania kompilatora pojawia się kod źródłowy samej aplikacji. Nie ma wiele mowy o testach integracyjnych i pisaniu czegokolwiek innego niż przekrojowych najpierw.
    No właśnie - są różne style TDD. Natomiast chciałem pokazać, że krok od testów integracyjnych do testów end-to-end nie jest już taki duży.

    OdpowiedzUsuń
  3. Ach, czy mam zatem rozumieć, że w tej książce podejście jest zbyt radykalne i Ty stosujesz jego odmianę, potencjalnie lżejszą, może nawet możnaby ją nazwać bardziej przystępną?

    OdpowiedzUsuń
  4. Muszę przyznać, że powyższy opis książki doprowadził mnie do pewnego dysonansu związanego z nazewnictwem. Zawsze byłem przekonany, że tego typu testy end-to-end to nie TDD tylko BDD.

    Ale mogę się mylić.

    OdpowiedzUsuń
  5. @Jacek: tak, coś takiego miałem na myśli. Stosuję TDD inaczej niż autorzy książki, a czy jest to lżejsze i bardziej przystępne, to trudno mi ocenić, nie spróbowawszy wersji proponowanej przez nich.

    W każdym razie, grunt to pisać testy najpierw.

    @streser: BDD rozumiem tak, że jest to zmiana sposobu myślenia i języka na taki, który akcentuje, że chodzi o zachowanie a nie testowanie. Tak przynajmniej rozumiem ten artykuł. Pisząc w stylu BDD na pewno łatwiej wyrazić wysokopoziomowe wymagania i rzeczywiście uzyskać testy end-to-end. Ale ja bym tego nie łączył tak bardzo, tzn. mogą być testy end-to-end w TDD i możemy używać BDD do testów jednostkowych.

    OdpowiedzUsuń