Szukasz najlepszych programistów Pythona? Znajdziesz ich w kilka kliknięć.

Test-driven development w Pythonie: dlaczego i jak to robić?

18 kwi 20228 min czytania

Dawid Karczewski

Senior full stack developer i CTO w Ideamotive.

test driven development w pythonie

Współczesne środowisko wytwarzania oprogramowania jest na tyle zróżnicowane, że mieści w sobie różne podejścia do kodowania. Najstarsze i najbardziej konwencjonalne to metoda sterowana architekturą. Polega na tym, że aplikacja powstaje ze wszystkimi pożądanymi funkcjami, a testy uruchamia się dopiero potem. U jej podstaw leży więc koncepcja „test-last”.

 

Podejście to wywodzi się z czasów, gdy zwinne wytwarzanie oprogramowania nie było jeszcze powszechnie stosowane. Największą wadą tej metody jest to, że cały zespół musi godzinami, a nawet całymi dniami przeglądać całą bazę kodu, żeby poprawić jedną linijkę, gdy któryś z testów nie przechodzi. 

 

Przyzwoitą alternatywą dla podejścia sterowanego architekturą jest test-driven development (TDD). Metoda pojawiła się w 1999 roku jako koncepcja „test-first”, będąca elementem extreme programming (XP). Później TDD stało się jednak samodzielną metodyką.  

 

Test-driven development proponuje kolejność działań odwrotną do programowania sterowanego architekturą: testy dla każdej jednostki kodu powstają, zanim pojawi się sam kod. Na pierwszy rzut oka może to brzmieć nieracjonalnie i czasochłonnie. Efekt końcowy jest jednak odwrotny: na produkcję trafia wyłącznie czysty kod, bez zbędnego czasu traconego na poprawianie błędów. 

 

Czy test-driven development ma sens w każdym projekcie? Czy da się je zastosować w programowaniu w Pythonie? Gdzie znaleźć programistów Pythona z praktycznym doświadczeniem w TDD? Ten wpis odpowiada na te pytania i przedstawia podstawy test-driven development.

Czym jest test-driven development?

TDD to technika wytwarzania oprogramowania oparta na bardzo krótkich, powtarzalnych cyklach złożonych z trzech etapów:

  • Najpierw powstaje test dla konkretnej funkcji;
  • Następnie pojawia się jednostka kodu, która ma ten test przejść;
  • Refaktoryzacja czystego kodu następuje, gdy kod pomyślnie przechodzi test.

Jednym z kluczowych aspektów test-driven development jest to, że programiści muszą tworzyć testy sami, bez przydzielania testerów. Zespoły trzymające się z dala od TDD mogą argumentować, że pisanie testów to nie sprawa programisty. Jest to prawdą dla tradycyjnego podejścia test-last, w którym testy uruchamia się post factum, po zakończeniu danego etapu prac. W efekcie pojawiają się typowe wady metody test-last:

  • Obniżona jakość produktu końcowego;
  • Testerzy stają się chłopcami do bicia w kwestii odpowiedzialności za jakość;
  • Koszt debugowania jest nadmiernie wysoki, zwłaszcza po wdrożeniu;
  • Niezadowoleni (lub tylko częściowo zadowoleni) klienci nie wracają do programistów z nowymi zleceniami.

Programiści pracujący w TDD myślą inaczej: dla każdego modułu powinien istnieć test, zanim programista napisze do niego kod. Testy modułów muszą więc tworzyć wyłącznie programiści. Co więcej, pierwszy test zawsze kończy się niepowodzeniem, bo kodu jeszcze nie ma. Dlaczego tak? Warto przyjrzeć się typowemu cyklowi TDD dokładniej.

Jak działa cykl test-driven development

Zwolennicy TDD dzielą cykl wytwarzania na trzy etapy, a każdy z nich na kilka kroków. Pierwszy, „czerwony” etap składa się z następujących kroków:

 

Krok 1: zastanów się nad modułem kodu, który ma powstać dla konkretnej funkcji;

 

Krok 2: napisz test modułu;

 

Krok 3: uruchom test, który nieuchronnie zakończy się niepowodzeniem.

 

Nie bez powodu część programistów nazywa krok 2 „pisaniem testu, który nie przechodzi”: nie ma jeszcze kodu zdolnego go zaliczyć. „Żaden test nie zasługuje na zaufanie, dopóki choć raz nie zawiedzie” - mówią. To rodzaj automotywacji: nieudany test skłania programistów do zastanowienia się nad poprawnością funkcjonalną przyszłego kodu albo nad samym testem. 

 

Drugi, „zielony” etap obejmuje:

 

Krok 4: napisz minimalny możliwy kod, który przejdzie test. Jeśli test świeci na zielono, funkcjonalność kodu jest poprawna. Kluczowe jest, żeby kod raz test zaliczył, a raz go nie zaliczył. O pomyłkę łatwo: testy zawsze przechodzące, tak samo jak zawsze niezaliczane, nie wymagają stworzenia niczego szczególnego. Ale test, który może zarówno przejść, jak i nie przejść, na pewno sprawdza jakąś logikę. 

 

Krok 5: Uruchom test ponownie, żeby upewnić się, że funkcjonalność kodu działa poprawnie.

Ostatni etap test-driven development, „refaktoryzacja”, oznacza pisanie czystego kodu bez zbędnych elementów i powtarzanie kroków od 1 do 5. Im krótszy cały cykl, tym lepiej. 

Jakie korzyści daje test-driven development

Choć test-driven development trudno uznać za uniwersalne podejście stosowane w każdym projekcie, część oczywistych korzyści TDD obejmuje szerokie spektrum praktyk programistycznych. Poniższe potrafią zoptymalizować pracę nad kodem - to tylko kilka z nich:

  • Programiści lepiej rozumieją, jaki ma być efekt końcowy, gdy tworzą testy sprawdzające wynik przed napisaniem kodu;
  • Każdy moduł kodu uznaje się za skończony, gdy kod przechodzi test. Zaliczenie testów i refaktoryzacja poprzedzają pracę nad kolejnym modułem;
  • Ponieważ zestaw testów jednostkowych uruchamia się po każdej refaktoryzacji, informacja zwrotna o tym, że każdy komponent działa, staje się ciągła;
  • Testy modułów pełnią rolę faktycznej dokumentacji, która zawsze odpowiada danym;
  • Gdy pojawia się błąd, programista tworzy test, żeby ustalić, na czym on polega. Następnie powstaje kod, który naprawia błąd i przechodzi test. Skraca to czas debugowania. Zaliczenie wszystkich pozostałych testów daje pewność, że cała funkcjonalność działa poprawnie;
  • Programiści mogą podejmować decyzje projektowe i refaktoryzować w dowolnym momencie, uruchamiając zaliczone testy, żeby mieć pewność, że system działa dobrze. Dzięki temu oprogramowanie łatwo naprawić;
  • Każdy kolejny test dodatkowo sprawdza wszystkie wcześniej wykryte błędy, więc powtarzalność podobnych błędów spada;
  • Ponieważ duża część testów odbywa się już na etapie wytwarzania, okres testów przed wdrożeniem jest krótszy.

Mity na temat test-driven development

Gdy firma zatrudnia programistów Pythona nieobeznanych z test-driven development, może pojawić się pewien opór, wynikający z uprzedzeń i mitów krążących wśród programistów. U ich podłoża leży brak wiedzy o podejściu test-driven. Choć potrafią brzmieć bardzo logicznie, kontrargumentów jest dość, by obalić każde błędne wyobrażenie o test-driven development.

 

Mit 1: TDD to wyłącznie testowanie i automatyzacja testów.

 

Wyjaśnienie: TDD to metodyka programowania oparta na podejściu „test-first”.

 

Mit 2: TDD oznacza brak projektowania.

 

Wyjaśnienie: TDD obejmuje analizę krytyczną i projektowanie zgodne z wymaganiami technicznymi.

 

Mit 3: TDD działa na poziomie pojedynczego modułu kodu.

 

Wyjaśnienie: Nic nie stoi na przeszkodzie, by stosować TDD zarówno na poziomie integracji, jak i systemu.

 

Mit 4: TDD nie sprawdza się w projektach z tradycyjnymi cyklami testowymi.

 

Wyjaśnienie: TDD jest popularne w extreme programming i innych odmianach zwinnego wytwarzania. Jednocześnie nic nie stoi na przeszkodzie, by stosować TDD na etapie wytwarzania dowolnego tradycyjnego projektu, obok jego etapu testów po zakończeniu prac.

 

Mit 5: TDD to tylko narzędzie.

 

Wyjaśnienie: TDD to metodyka, w której każdy nowy test modułu trafia do zestawu testów automatycznych. Wszystkie testy trzeba uruchamiać po każdej zmianie w istniejącym kodzie oraz po każdej refaktoryzacji. Różne narzędzia automatyzacji testów ułatwiają więc TDD, które jest czymś więcej niż tylko narzędziem. 

 

Mit 6: TDD oznacza przeniesienie testów akceptacyjnych z testerów na programistów.

 

Wyjaśnienie: TDD nie zamienia testerów na programistów w testach akceptacyjnych, ponieważ zestaw specyficznych testów skoncentrowanych na wytwarzaniu - takich jak testy jednostkowe i testy modułów - uruchamiają programiści na etapie wytwarzania.

Kiedy warto stosować test-driven development

Test-driven development może zostać przyjęte jako metodyka przez programistów Pythona, gdy ich projekt spełnia określone warunki. Część z nich należy do warunków zewnętrznych, a część do wewnętrznych. Wybór zależy więc od następujących czynników:

  1. Zespół lub osoba, która wyznacza cele projektu. Chodzi o kwalifikacje techniczne klienta.
  2. Procesy wewnętrzne, które przechodzą przez przepływy pracy w projekcie. TDD trudno wdrożyć, jeśli nie stosuje się ciągłej integracji. 

Poniższe kryteria pomagają rozpoznać przypadki, w których TDD ma sens:

  • Jeśli istnieje jasny opis funkcji i modułów, wyzwaniem jest przyspieszenie i optymalizacja cyklu życia wytwarzania oprogramowania. W takim przypadku programiści nie muszą sami wymyślać całej architektury. Mogą pisać testy modułów, mając jasną wizję docelowego systemu. W przeciwnym razie ryzykują stworzenie czegoś innego, niż oczekują klienci;
  • Jeśli podejście do funkcji i modułów jest typowe, tzn. dostępna jest jednolita logika budowania architektury. W takim przypadku programiści mogą przygotować środowisko testowe z pewną liczbą szablonów. W przeciwnym razie każdy test wymaga unikalnych warunków, co jest zbyt czasochłonne, by TDD przyniosło jakąkolwiek wartość;
  • Jeśli zadania stawia się w stylu „jeden moduł kodu to jeden test”. Gdy moduł można podzielić na kilka podmodułów, pojedynczy test nie wystarczy. 
  • Jeśli istnieje wysokopoziomowy framework testowy, który obsługuje testy integracyjne i odpowiada głównej technologii projektu. Lepiej mieć takie narzędzie w swoim języku programowania (Python w naszym przypadku, ale o tym za chwilę);
  • Jeśli w zespole inżynierów nie ma samodzielnych testerów. Gdy testy piszą testerzy, znika jedna z głównych zalet TDD - mentalna więź między programistą a funkcjonalnością.  

Jak widać, test-driven development nie jest panaceum. Jeśli TDD nie pasuje do Twojego projektu, prace zwalniają, a programiści dochodzą do błędnych uogólnień, takich jak:

  • TDD nadaje się tylko dla firm produktowych;
  • Nie mamy odpowiedniego stosu technologicznego, żeby stosować TDD;
  • Metodyka TDD w ogóle się nie sprawdza;
  • TDD pasuje tylko do backendu;
  • Nasza dziedzina oprogramowania nie ma nic wspólnego z TDD itd.

Żeby uniknąć takich ponurych wahań, warto ocenić TDD z perspektywy własnej działalności programistycznej. Test-driven development raczej nie jest zalecane, jeśli:

  • Zlecenie na oprogramowanie składa klient bez wystarczającego zaplecza technicznego. Programiści muszą sami przemyśleć wszystkie możliwe scenariusze wraz z całą architekturą. Pisanie testów modułów grozi wtedy nadmierną czasochłonnością i nieefektywnością. Lepiej najpierw zatwierdzić funkcjonalność aplikacji z klientem;
  • Logika biznesowa aplikacji nie jest wcześniej zaprojektowana. Programiści nie mają jasnego pojęcia, jak funkcjonalność ma działać. Testy pisane przed kodem będą stale przesuwać terminy projektu przy słabo zrozumiałej funkcjonalności;
  • W projekcie występują skomplikowane obliczenia lub duże obciążenia. Wyników złożonych obliczeń trudno przewidzieć na etapie pisania testów jednostkowych. 

To, czy stosować TDD, jest kwestią indywidualną. Wiele zależy od projektu, liderów zespołu programistycznego i wartości panujących w firmie. TDD nie łamie wzorców, jeśli pamięta się o kilku podstawowych zasadach:

  • Testy powinny pokrywać funkcjonalność, a nie kod;
  • Testy powinny być oderwane od implementacji kodu;
  • Testy powinny działać w swoim własnym środowisku.

Dlaczego Python pasuje do TDD jak mało co 

To, czy Python jest odpowiednią technologią do test-driven development, wydaje się pytaniem bezprzedmiotowym, jak uważa wielu inżynierów programowania w Pythonie. To właśnie dostępność odpowiednich zestawów testów automatycznych i frameworków decyduje o tym, jak dobrze dana technologia sprawdza się w TDD. Python jest w tym kontekście jednym z niewielu języków programowania na tyle bogatych w narzędzia do nowoczesnych praktyk testowych.

 

Python słynie z prostej czytelności i łatwości pisania kodu. Inżynierowie automatyzacji w Pythonie nie muszą używać kompilatorów do konwersji kodu, ponieważ jest to język skryptowy. Przewodnik po automatyzacji „Zen of Python” mówi, że testy napisane w zestawie testowym są łatwe do czytania i wyjaśnienia.

 

Selenium to framework automatyzacji, który pozwala na przykład tworzyć testy automatyczne w przeglądarkach z elegancką prostotą. Skrypty testów automatycznych powstają dzięki współpracy Selenium WebDrive z takimi przeglądarkami jak Mozilla Firefox, Google Chrome, Internet Explorer, Safari i Opera. Narzędzie jest kompatybilne z Pythonem i innymi językami skryptowymi, co pozwala tworzyć szybkie, powtarzalne testy w pipeline’ach continuous integration.

 

Kolejnym frameworkiem testów automatycznych skoncentrowanym na Pythonie jest Robot. Framework pozwala programistom pisać kod dopiero wtedy, gdy istnieje niezaliczony test. Trudno o bardziej wyrazistą cechę test-driven development. Robot jest napisany w Python i skierowany do organizacji, które praktykują Agile i TDD.

 

Wielu programistów Pythona uważa PyTest za najlepszy framework testów automatycznych w historii. Testy jednostkowe, end-to-end i integracyjne łatwo w nim zaimplementować. Niewielu programistów Pythona woli domyślny zestaw testowy PyUnit (unittest) od PyTest, bo ten drugi daje znacznie szerszy zakres opcji testowania. 

 

Poza frameworkami testowymi dostępne są też dedykowane biblioteki Pythona nadające się do TDD. Hypothesis to na przykład ta, która pomaga tworzyć mocniejsze i prostsze w pisaniu testy jednostkowe, potrafiące znaleźć słaby kod tam, gdzie trudno się go spodziewać. Biblioteka bezproblemowo współpracuje z każdym zestawem testowym.

 

Jak widać, Python jest bogaty w zestawy narzędzi testowych, które czynią test-driven development bardzo odpowiednim podejściem do niemal każdego oprogramowania napisanego w Pythonie.

Podsumowanie

Odpowiedź na pytanie „dlaczego warto stosować test-driven development” ma kilka możliwych wariantów. Przede wszystkim TDD to bezkompromisowa jakość kodu dopracowanego dzięki licznym refaktoryzacjom. Debugowanie odbywa się organicznie przez cały cykl życia wytwarzania.

 

Kolejny aspekt to czas (a więc i pieniądze) zaoszczędzony dzięki krótszym testom akceptacyjnym przed wdrożeniem. Znacznie bardziej elegancki kod powstaje w krótszym czasie, gdy progresywne podejścia zwinne, takie jak test-driven development, idą w parze z continuous integration/continuous delivery. 

 

Niewiele technologii jest bardziej zgodnych z TDD niż Python: dla tego języka dostępnych jest wiele narzędzi do testów automatycznych. Test-driven development w Pythonie to przepis na sukces dla klientów, którzy nadążają za duchem czasu. 

 

Skontaktuj się z nami już dziś, jeśli podzielasz naszą wizję idealnego połączenia „TDD + Python”, które sprosta wymaganiom każdego projektu programistycznego. Najwyższej klasy usługi programistyczne i dedykowane zespoły profesjonalnych programistów Pythona są zawsze dostępne w rozsądnej cenie.

Dawid Karczewski

Dawid jest full stack developerem. Tworzy aplikacje w Ruby on Rails i React Native od pierwszego pomysłu aż po wdrożenie. Buduje dla naszych klientów rozwiązania, które pomagają im rosnąć.

Zobacz wszystkie wpisy autora
Python_ The Definitive Business Guide

Python: kompletny przewodnik biznesowy

Dla cyfrowych przedsiębiorców i product ownerów

Czytaj teraz
Newsletter 9-1
Newsletter Ideamotive
Twój dwutygodniowy przegląd najgorętszych newsów technologicznych

Szukasz ekspertów od Pythona do swojego zespołu?

W naszej sieci talentów czekają dziesiątki zweryfikowanych specjalistów Python.