Test-Driven Development – pisanie dobrych testów

Mimo, że nie ma jedneje definicji dobrego testu jako takiego, istnieją natomiast wytyczne i różnego rodzaju heurystyki, dzięki którym można stwierdzić czy pisane przez nas testy są na przyzwoitym poziomie. O ile wraz z narzędziami do tworzenia testów jednostkowych dostarczane są liczne wzorce, to o tyle dla procesu budowania testów akceptacyjnych na podstawie wymagań użytkowników końcowych można zastosować dwie zasady:

  • zasadę atomowości,

  • zasadę izolacji.

W ogólności oznacza to że testy powinny być niewielkich rozmiarów, scentralizowane, atomowe i niezależne od innych. Niezależność testów między sobą nie polega na wykonywaniu ich w losowej kolejności a raczej nie pokrywaniu się ich odpowiedzialności. Pisanie dobrych testów wymaga umiejętności programowania intencyjnego (ang. Programming by Intention). Pozwala to programiście na skupienie się na kodzie pod względem jego funkcjonalności aniżeli sposobie jego realizacji. Główne zalety to poprawa czytelności, a co za tym idzie jego łatwiejsza analiza i użycie. W takim kodzie można zrozumieć intencje autora kodu, jak i łatwo odnaleźć główne przebiegi procesów.

Lasse Koskela w książce „Test Driven: Practical TDD and Acceptance TDD for Java Developers” podaje kilka wskazówek, które wypracował podczas swojej długoletniej pracy z tą metodyką. Pierwsza z nich mówi o przestrzeganiu kolejności pracy nad projektem w tej metodyce. Szczególny nacisk kładzie na ostatnią fazę spośród wzorca „test – code – refactor”. Jako główny problem przy tworzeniu systemów wskazuje brak wprowadzania poprawek w kodzie źródłowym klasy komponentu i testu.

Druga rada mówi, by jak najszybciej i jak najmniejszym kosztem doprowadzić do przejścia testów. Może to zabrzmieć dość niespodziewanie, ale etap kodowania
to etap tworzenia jak najprostszej i jak najbardziej trywialnej implementacji, która dopiero w fazie refactoringu nabiera ostatecznego kształtu

Przy każdym wystąpieniu błędu należy zwolnić tępo pracy i poświecić go więcej na implementację. Wiele błędów pochodzi z niedbalstwa i pośpiechu w realizacji zadania.

Za dobrą praktykę przyjęło się unikanie redundancji kodu w momencie projektowania środowiska uruchomieniowego dla testów. Poprzez środowisko uruchomieniowe rozumiane jest wszystko to, czego potrzebujemy przy przeprowadzeniu procesu testowania, skonfigurowane w odpowiedni sposób. Począwszy od wirtualnej maszyny Javy, a na inicjalizacji obiektu testowanego skończywszy. Tak skonfigurowany kontekst (ang. fixture) musi być wspólny dla uruchamiania wszystkich testów i nigdy nie powinien być tworzony oddzielnie dla każdej jednostki. Dzięki stworzeniu takich warunków uzyskujemy prostotę i łatwość w tworzeniu testów, gdyż cały kontekst uruchomienia posiadamy skonfigurowany już na starcie i możemy skupić się tylko i wyłącznie na pisaniu testu.

Rodzaje testowania

Jednak największą bolączką testerów oprogramowania są przypadki, gdzie obiekt
do walidacji jest mocno powiązany z zasobami zewnętrznymi, takimi jak dostęp do dysku czy połączenia z bazą danych. Na szczęście powstało rozwiązanie, znane jako Test Double. Są to obiekty tworzone jako odpowiedniki zasobów zewnętrznych, które realizują ich odpowiedzialność w sposób szybszy i to pod względem czasu wykonania kodu, jak i czasu potrzebnego na napisanie i zarządzanie testem. Cykl pracy z Test Double zaczyna się od jego stworzenia, skonfigurowania jego zachowania, a następnie użycia w kodzie testu, by sprawdzić poprawność implementacji produkcyjnej.

Zanim zostaną przedstawione rodzaje obiektów Test Double, które są bardzo istotne przy testowaniu, dokonam rozdzielenia pomiędzy rodzajami testów.

Pierwszymi z nich są testy stanu komponentu (ang. State-Based Testing), które porównują wartości przechowywane w polach komponentu z naszymi oczkiwaniami zawartymi w wywołaniach metod mocków.

Z drugiej strony, możemy chcieć zweryfikować sposób realizowania zależności pomiędzy testowanymi obiektami, bardziej niż badać jego stan końcowy. Ta metoda skupia się na walidacji czy testowany obiekt wywołał odpowiednie metody z odpowiednimi parametrami. Nosi ona nazwę testowania bazującego na interakcjach (ang. Interaction-Based Testing) i jest używana do walidacji bardziej złożonych obiektów z wieloma zależnościami, gdzie proste sprawdzanie stanu nie wystarcza.

Dodaj komentarz