Atomówki

Celem testów automatycznych jest znalezienie błędów. To oczywiste, jednak testując oprogramowanie nie można ograniczać się wyłącznie do stwierdzenia „Nie działa” (chyba że chcemy bardzo szybko utracić sympatię programistów). Dobrze napisane testy powinny jasno określać co i w jakich okolicznościach nie działa.

Co zrobić, żeby nasze testy automatyczne były rzeczywiście pomocne w procesie rozwoju oprogramowania?

Jakie powinny być przypadki testowe?

Projektując testy należy pilnować się trzech, powiązanych ze sobą zasad: Przypadki testowe powinny być: Małe, Atomowe, Autonomiczne.

  • Małe
    Testy automatyczne sprawdzają się, kiedy zapewniają możliwie szybką informację zwrotną dla programistów – jeżeli ich wykonywanie trwa zbyt długo, informacja ta stopniowo traci na wartości (bo programista nie czeka na wyniki testów, tylko zajmuje się kolejnymi zadaniami, z czasem będzie mu coraz trudniej ponownie przestawić się na zadanie, które uważał za zakończone, ponadto do kodu dodawane są również kolejne zmiany, które mogą mieć wpływ na pozostałą część oprogramowania).
    W sytuacji idealnej, kiedy możemy przeprowadzić każdy test równolegle – czas wykonywania testów jest równy czasowi wykonywania najdłuższego z nich. Wyobraźmy sobie zatem sytuację, w której mamy 1 test wykonujący się 5 min, jeżeli możemy rozbić go na 3, trwające 1.5 min, uda nam się skrócić wykonywanie testów blisko trzykrotnie. Nawet nie mając możliwości korzystania z wirtualnych maszyn i  równoległego odpalania testów należy tak projektować poszczególne przypadki testowe, aby ich wykonywanie trwało maksymalnie krótko.
  • Atomowe
    Chcąc zapewnić precyzyjną informację zwrotną, powinniśmy tworzyć przypadki testowe charakteryzujące się atomową budową, tzn. takie, które sprawdzają pojedynczą funkcjonalność – implementacyjnym odzwierciedleniem tego założenia jest trzymanie się zasady jednej asercji w przypadku testowym. Dzięki temu, w przypadku niepowodzenia testu od razu wiadomo, „co poszło nie tak”.
  • Autonomiczne
    Autonomiczność przypadków testowych to rozwinięcie ich atomowości. Przypadki testowe powinny być tworzone tak, aby można je było wykonać w dowolnym zestawie i kolejności – nie powinny być od siebie w żadnym stopniu zależne. Poniżej znajduję się przykład źle stworzonego przypadku testowego który sprawdza trzy elementy: dodanie wiadomości do forum, wyświetlenie jej i edycję:
    W efekcie, niepowodzenie jednego kroku sprawia, że pozostałe etapy zostaną przetestowane dopiero kiedy zostanie on naprawiony. Błędnym podejściem jest również stworzenie trzech przypadków testowych, odwołujących się do wiadomości stworzonej w innych przypadkach testowych:
    Widać wyraźnie, że przy tak zaprojektowanych przypadkach testowych, kolejność ich wykonywania nie jest bez znaczenia: nie możemy zatem pozwolić sobie na swobodę równoległego (a zatem szybszego) wykonywania testów, ponadto: jeżeli wystąpi błąd w kroku tworzenia nowej wiadomości – wszystkie kolejne przypadki również zakończą się niepowodzeniem.
    Rozwiązaniem jest wykorzystanie w testach danych, umieszczanych bezpośrednio w bazie danych – lub (chociaż to mniej pewne rozwiązanie) z pominięciem warstwy frontu (jeżeli testujemy front!) np. poprzez API:
    W takim podejściu zapewniamy sobie łatwość diagnozowania błędów, szybkość ich identyfikacji i możliwość wykonania wszystkich testów, niezależnie od wcześniej znalezionych błędów.

Kilka słów na koniec

Powyższe założenia mają charakter idealny, oczywistym jest, że w rzeczywistym świecie nie uda nam się spełnić ich wszystkich. Przykładem mogą być testy E2E, aplikacji które wymagają zalogowania użytkownika – tego kroku najprawdopodobniej nie ominiemy i będziemy go powtarzać w każdym przypadku testowym. W ogromnej jednak większości, umieszczanie danych testowych bezpośrednio w bazie danych pozwala nie tylko na zapewnienie autonomiczności testów, ale również wpływa na szybkość ich wykonywania.

Ogromnie ważnym jest również pamiętanie o posprzątaniu po sobie (czyli usunięciu wszystkich danych testowych po zakończeniu testu).

Komentarze