Przez ostatnie kilka miesięcy pracowałem nad bardzo ekscytującym projektem, w którym wykorzystywaliśmy Domain Driven Design oraz testy pisane zgodnie z podejściem BDD (Behaviour Driven Development) oraz zasadą “testuj najpierw”. Największe pokrycie kodu było na poziomie testów integracyjnych, a testy jednostkowe wykorzystywane były głównie do weryfikacji skomplikowanej logiki biznesowej naszych bogatych modeli.


Artykuł przygotował dla nas Maksymilian Majer. CEO itCraftship. Maksymilian jest inżynierem i architektem oprogramowania z trzynastoletnim doświadczeniem. Jego pasją jest optymalizacja i automatyzacja procesów wokół jakości oprogramowania oraz pracy w zdalnych zespołach. Od ponad ośmiu lat jest związany z branżą startup’ów także od strony biznesowej, ale nigdy nie rozstaje się z kodem na zbyt długi czas. Obecnie pomaga firmom IT wdrażać kulturę pracy zdalnej i budować efektywne zespoły rozwoju produktu.


Żeby było zabawniej, DDD stosowaliśmy w projekcie pisanym w Python + Django, więc niestety trzeba było iść na pewne kompromisy (brak value objects, niemożliwa enkapsulacja, itp.), ale to materiał na zupełnie odrębny artykuł. Mimo to nasze podejście do testów sprawdzało się, mieliśmy dobrą informację zwrotną w przypadku wprowadzenia regresji, a po zastosowaniu sewera continuous integration (Semaphore CI w naszym przypadku) bardzo usprawnił się nasz przepływ pracy. Natomiast brakowało dodatkowej warstwy, która wykrywałaby regresje na poziomie interfejsu użytkownika.

W związku z tym, że na froncie nadal wykorzystywane jest leciwe jQuery i nie korzystamy niestety z nowoczesnych bibliotek typu React, Vue czy Angular, testowanie widoków było terenami, na które zespół niechętnie się zapuszczał.

Pierwsze podejście do wprowadzenia testowania interfejsu (tzw. end to end testing lub e2e) zrobiliśmy krótko po tym, kiedy zacząłem współpracę z zespołem. Na tamtym etapie rozważaliśmy wiele podejść, m.in. następujące:

  • Puppeteer
  • NightmareJS z xvfb
  • Cypress
  • Protractor + Selenium + Headless Chrome lub Chrome z xvfb
  • Behave + Splinter + Selenium
  • Behave + Selenium Python driver

Po dłuższej analizie (m.in. po tym świetnym artykule) zdecydowaliśmy się na podejście Behave + Splinter + Selenium. Najważniejszym powodem było to, że nie chcieliśmy wprowadzać dodatkowej komplikacji w postaci projektu w JavaScript i myśleliśmy, że dobrym pomysłem będzie ponowne użycie specyfikacji z naszych testów integracyjnych. Sugerowaliśmy się m.in. tym cytatem z dokumentacji projektu behave django:

„If you want to test/exercise also the “user interface”, it may be a good idea to reuse the feature files, that test the model layer, by just replacing the test automation layer (meaning mostly the step implementations). This approach ensures that your feature files are technology-agnostic, meaning they are independent how you interact with “system under test” (SUT) or “application under test” (AUT)”.

Od początku napotykaliśmy problemy. Chcieliśmy odpalać testy na przeglądarce Google Chrome, ponieważ większość użytkowników aplikacji właśnie z tej przeglądarki korzysta. W środowisku lokalnym miało to sens, ale odpalanie na serwerze CI wymagało wprowadzenia konteneryzacji. Biorąc pod uwagę nasze nieduże doświadczenie na tamten czas z Docker Compose i ogólnie konteneryzacją aplikacji, ciężko było nam zestawić usługi tak, żeby kontener z Selenium łączył się naszą aplikacją w innym kontenerze.

Kiedy wszystko udało się lokalnie, to na serwerze CI testy „wysypywały” się w nieprzewidywalny sposób. Zależało nam na wiarygodnej, przewidywalnej i zautomatyzowanej informacji na temat wprowadzonych regresji, a nie dochodzeniu, czy testy nie przechodzą ze względu na środowisko lub jeszcze inne powody. W końcu testy, których się nie odpala są równie słabe, jak brak testów.

Po tej nieudanej próbie tymczasowo porzuciliśmy nasze zapędy, żeby testować interfejs użytkownika i koncentrowaliśmy się nadal na naszych testach integracyjnych. Testowanie regresji w interfejsie użytkownika pozostało manualne i wykonywane na głównych scenariuszach użycia aplikacji za każdym razem, kiedy mieliśmy wprowadzać nowszą wersję aplikacji na produkcję. Jednym słowem: porażka.

Drugie podejście do testowania UI

Niedawno natknąłem się na interesujący projekt o nazwie CodeceptJS. Zaintrygowała mnie jego trywialna składnia, bliska naturalnemu językowi. Zdawało mi się, że nawet średnio-techniczna osoba jest w stanie zrozumieć testy pisane z użyciem CodceptJS. Ponadto CodeceptJS wprowadza dodatkową warstwę abstrakcji nad API udostępniane przez narzędzia do automatyzacji przeglądarek (Selenium, NightmareJS, Protractor, Puppeteer czy Appium) ujednolicając sposób w jaki wchodzimy z nimi w interakcję.

W międzyczasie zdobyliśmy więcej doświadczenia z dockerem i fakt, że projekt jest w JavaScript nie bolał już tak bardzo. Korzystając z docker-compose oraz prywatnego kontenera w rejestrze Docker Hub mogliśmy wydzielić testy do osobnego repozytorium, które tylko korzysta z obrazu naszej aplikacji w Pythonie. Tym samym nie bałaganimy dodatkowymi zależnościami ze świata JavaScriptu (i stricte testów e2e) w naszym głównym repozytorium aplikacji, która jest pisana w Pythonie.

Zachęciło nas to do podjęcia kolejnej próby do testów e2e, teraz bogatszych już o doświadczenia z poprzedniej porażki.

Poniżej przedstawiam przykłady testu logowania błędnymi danymi i jak wygląda to w popularnych narzędziach do automatyzacji. Dla porównania jest tam też test napisany z użyciem CodeceptJS, który będzie wyglądał tak samo niezależnie od tego, z którym narzędziem zostanie użyty.

Gist: https://gist.github.com/maksymilian-majer/797fa346a8a94eee5633ad0b36e284f1

Zobacz rezultat naszego testu w CodeceptJS i jak zgrabnie wypisane są polecenia w stylu BDD, które pisane są niemal naturalnym językiem:

Dodatkowym atutem CodeceptJS jest to, że w standardzie robi zrzut z ekranu, kiedy test nie przeszedł – pomaga to dojść dlaczego tak się stało.

Po pierwszych próbach z tym narzędziem widzieliśmy już, że testowanie E2E będzie od teraz dużo bardziej przyjemne. Nasze testy są pisane z użyciem czytelnego, ekspresyjnego API a ich wyniki są czytelne nawet dla nietechnicznego personelu. Przede wszystkim tworzenie testów jest łatwiejsze a ich rezultaty bardziej przewidywalne, co sprawia, że oszczędzamy czas na dochodzeniu, czemu nie działają i śpimy spokojnie o wprowadzanie regresji w UI. Dodatkowym bonusem jest łatwiejszy on-boarding testerów, ponieważ praca z CodeceptJS nie wymaga dużego doświadczenia w testowaniu automatycznym.

W dalszej części artykułu zamierzam pokazać, na przykładzie popularnego projektu WordPress, jak udało nam się uzyskać przewidywalne rezultaty z testów i cieszyć większym spokojem przy wprowadzaniu nowych wersji naszej aplikacji.

Zapraszamy do dyskusji
1 2 3 4 5
Nie ma więcej wpisów

Send this to a friend