Cucumber zrobiony dobrze: BDD, które rozmawia z ekspertami domeny
Cucumber jest albo mostem do twoich ekspertów domeny, albo kosztowną warstwą pośrednią — a to, który, zależy w całości od tego, jak go piszesz. Oto gdzie się opłaca, gdzie nie i jakie reguły stosujemy.
Niewiele narzędzi w świecie Ruby dzieli tak jak Cucumber. Dla zwolenników to rzecz, która wreszcie pozwala nieprogramistom czytać, a nawet pisać kryteria akceptacji napędzające development. Dla przeciwników — a są głośni — to zbędna warstwa tłumaczenia zamieniająca pięciolinijkowy test w plik feature, regex i definicję kroku, wszystko po to, by przetestować coś, co zwykły test Capybary przetestowałby wprost.
Oba obozy mają rację i to jest cały sens. Cucumber to narzędzie o dużej mocy i wąskim przeznaczeniu, a niemal całe rozczarowanie nim bierze się z używania go do złej roboty. Oto jak odróżnić jedno od drugiego i jakie reguły stosujemy, by trzymać go po właściwej stronie.
Czym Cucumber naprawdę jest
Cucumber wykonuje specyfikacje w zwykłym języku. Piszesz scenariusze w Gherkinie —
małej, ustrukturyzowanej gramatyce angielskiego Given/When/Then — i każdą
linię wspierasz definicją kroku w Ruby, która wykonuje akcję lub asercję:
Feature: Zwroty
Jako klient
Chcę zwrócić przedmiot w ciągu 30 dni
Aby odzyskać pieniądze
Scenario: Zwrot w oknie zwrotów
Given kupiłem "Wełniany szalik" 10 dni temu
When proszę o zwrot za "Wełniany szalik"
Then powinienem otrzymać zwrot 25,00 zł
And przedmiot powinien być oznaczony jako zwrócony
Given("kupiłem {string} {int} dni temu") do |product, days|
@order = create_order(product: product, placed_at: days.days.ago)
end
When("proszę o zwrot za {string}") do |product|
RefundService.new(@order).refund!(product)
end
Then("powinienem otrzymać zwrot {float}") do |amount|
expect(@order.refunds.last.amount).to eq(amount)
end
Plik feature jest specyfikacją; definicje kroków są klejem między tym językiem a twoim systemem. To rozdzielenie jest całym powodem istnienia Cucumbera — i zarazem źródłem każdej skargi na niego.
Linia podziału: kto czyta pliki feature?
Oto jedno pytanie, które rozstrzyga, czy Cucumber się opłaca: czy nieprogramista naprawdę czyta lub pisze te scenariusze?
Jeśli product owner, ekspert domeny, analityk QA albo klient faktycznie otwiera pliki feature — by uzgodnić zachowanie przed zbudowaniem, sprawdzić je po albo dodać scenariusz samodzielnie — to Cucumber robi coś, czego nie potrafi nic innego. Gherkin jest współdzielonym artefaktem, kontraktem napisanym w języku biznesu, który przy okazji jest wykonywalny. To ogromnie cenne na projekcie o realnej złożoności domenowej, z interesariuszami, którym zależy na dokładnych regułach: okna zwrotów, kwalifikowalność ubezpieczeniowa, progi cenowe, ograniczenia regulacyjne.
Jeśli natomiast jedynymi, którzy kiedykolwiek czytają pliki feature, są programiści, którzy napisali definicje kroków, to Cucumber jest czystym narzutem. Wziąłeś test, rozrzuciłeś go na dwa pliki i wstawiłeś pośrodku warstwę dopasowania regex — cały koszt abstrakcji i żadnej z jej korzyści. W takim przypadku zwykły test feature RSpec/Capybara mówi to samo w jednym pliku bez podatku od tłumaczenia i to jego powinieneś użyć. My tak robimy, często. Cucumber nie jest domyślnym wyborem; jest świadomym wyborem do konkretnej sytuacji.
Reguły dla Cucumbera, który pozostaje czytelny
Gdy sytuacja faktycznie woła o Cucumbera, garść dyscyplin trzyma go po wartościowej stronie linii. Niemal każda historia „Cucumber jest okropny” jest tak naprawdę historią o łamaniu tych reguł.
Pisz scenariusze w języku domeny, nigdy UI. To ta najważniejsza. W chwili, gdy twoje kroki mówią o klikaniu przycisków i wypełnianiu pól, wartość biznesowa wyparowuje, a utrzymanie staje się koszmarem.
# ŹLE — to skrypt UI, nie specyfikacja
When wypełniam "email" wartością "a@b.com"
And naciskam "Wyślij"
And klikam trzeci wiersz tabeli
# DOBRZE — to zachowanie, które ekspert domeny przeczyta i które wciąż będzie
# prawdziwe po przeprojektowaniu strony
When loguję się jako powracający klient
And proszę o zwrot za moje najnowsze zamówienie
Kroki w języku domeny przeżywają przeprojektowania UI, czytają się jak wymaganie, które kodują, i mogą być implementowane albo przez UI, albo wprost wobec domeny — co czyni je też szybszymi.
Trzymaj definicje kroków cienkie. Krok powinien delegować do helpera, obiektu usługowego albo page objectu — a nie zawierać ściany logiki. Logiki w krokach nie da się reużyć ani przetestować i po cichu zamienia twój zestaw testów w drugą, gorszą aplikację.
Reużywaj kroki; nie rozmnażaj ich. Rozrośnięta, niemal zduplikowana biblioteka
kroków (jestem zalogowany, zalogowałem się, loguję się) to sposób, w jaki
zaczyna się przeklęty młyn „niezdefiniowanego/niejednoznacznego kroku”. Pielęgnuj to
słownictwo jak mały język domeny, którym ma być.
Używaj Background i tabel, by zostać DRY. Background uruchamia wspólne
przygotowanie przed każdym scenariuszem; tabela danych napędza jeden scenariusz na
wielu przypadkach. Oba trzymają feature deklaratywnymi, nie powtarzalnymi.
Pozwól warstwom robić swoje. Cucumber jest do zachowań akceptacyjnych end-to-end, na których zależy interesariuszowi. To złe narzędzie do wyczerpujących przypadków brzegowych, macierzy walidacji i szczegółów algorytmicznych — te należą do szybkich testów jednostkowych. Zdrowy projekt ma kilka wysoko wartościowych scenariuszy Cucumbera nad szeroką bazą testów jednostkowych RSpec, a nie setki wolnych scenariuszy przeglądarkowych próbujących pokryć każdą gałąź. Odwrócenie tej piramidy to sposób, w jaki zespoły kończą z dwudziestominutowym zestawem, którego nie znoszą.
Uczciwy koszt
Nawet dobrze zrobiony Cucumber ma cenę. Pośredniość jest realna: zawodzący scenariusz oznacza skakanie od feature do definicji kroku do helpera, by znaleźć przyczynę. Dopasowanie kroków potrafi zaskoczyć niejednoznacznością. A pełne scenariusze end-to-end napędzane przez przeglądarkę są wolne, więc musisz być bezwzględny co do tego, ile ich trzymasz. Nic z tego nie dyskwalifikuje — ale dlatego „czy ktoś poza zespołem dev to czyta?” musi być prawdziwym „tak”, a nie aspiracyjnym.
Nasz werdykt
Cucumber to narzędzie współpracy, które przy okazji uruchamia testy, a nie narzędzie testowe, które przy okazji ładnie się czyta. Na projekcie o realnej złożoności domenowej i z interesariuszami, którzy angażują się w reguły, napisanie tych reguł w wykonywalnym Gherkinie jest warte każdej odrobiny narzutu — łapie nieporozumienia, zanim staną się kodem, i trzyma żywą, zweryfikowaną specyfikację tego, jak biznes naprawdę działa. Na projekcie bez takich interesariuszy to ceremonia, a zwykły test feature jest uczciwym wyborem. Dobierz narzędzie do sytuacji, pisz kroki w języku domeny, a nie DOM, a Cucumber przejdzie z dzielącego do naprawdę użytecznego.