Notatki o testowaniu: Behaviour-driven development z easyb
Groovy to język, który przykuwa coraz większą uwagę entuzjastów technologii Javowych, a artykuły pojawiające się na łamach Java exPress tylko to potwierdzają. Jego niewątpliwymi zaletami są prostota i przejrzystość składni, które pozwalają zdecydowanie zwiększyć produktywność i uwolnić od monotonnych linijek kodu tak dobrze znanych nam z Javy.
W niniejszym artykule chciałbym przedstawić bardzo ciekawe i użyteczne narzędzie bazujące na Groovym. Zachwyca ono swoją prostotą i wszechstronnym zastosowaniem w testach, oraz, co najistotniejsze, pozwala na pisanie testów w myśl koncepcji behaviour-driven development.
Kilka(dziesiąt) słów wprowadzenia
Zanim jednak przejdziemy do przedstawienia easyb, warto zacząć od krótkiego wprowadzenia do coraz bardziej popularnej techniki behaviour-driven development. Na jakich założeniach się opiera? Czym różni się od test-driven development? Jakie narzędzia w Javie pozwalają pisać testy w oparciu właśnie o nią? Po przeczytaniu tego artykułu, poza wzbogaceniem słownika informatycznych żargonów o kolejny modny akronim, z pewnością będziesz znał odpowiedzi na te pytania.
Do zilustrowania koncepcji BDD posłuży nam prosty przykład. Załóżmy, że organizujemy konferencję o technologiach związanych z Javą i potrzebujemy stworzyć moduł rejestracji. Oczywiście, aby zachęcić potencjalnych uczestników powinniśmy zaoferować jakieś zniżki. Dla maksymalnego uproszczenia naszego przykładu umówmy się także, że cena wejściówki to 100,00 PLN. A zatem, do dzieła! Zaczynamy (jak zawsze zresztą, prawda?), od testu:
@Test public void testConferenceDiscount() { Participant p = new Participant(); p.setJavaUserGroupMember(true); RegistrationService registrationService = new RegistrationService(); PaymentDetails paymentDetails = registrationService.register(participant); assertEquals(80.0, paymentDetails.getPrice()); }
Nie trzeba mieć wielkiego doświadczenia w pisaniu testów, aby stwierdzić, że powyższy kod jest daleki od ideału. Przede wszystkim jest nieprecyzyjny. Nazwa metody tak naprawdę nie niesie ze sobą żadnej istotnej informacji. W momencie, gdy w module rejestracji pojawi się jakiś błąd, będziemy zmuszeni do wnikliwej analizy kodu takiej metody testowej, aby określić wymaganie definiujące warunki przydzielania zniżki. W powyższym wypadku nie stanowi to wielkiego wyzwania, ale zapewne wielu z Was musiało nie raz w swojej w karierze analizować bardziej "rozbudowane" testy. Co więcej, pisząc testy, zwłaszcza na początku przygody z test-driven development, bardzo często powraca pytanie "co ja tak naprawdę powinienem przetestować?". Nierzadko zdarza się nam skupiać bardziej na implementacyjnych szczegółach klas, których działanie chcemy weryfikować, niż na faktycznych wymaganiach, które mają spełniać. Między innymi z takimi właśnie problemami próbuje się mierzyć koncepcja behaviour-driven development.
Behaviour-driven development,
to podejście skupiające się przede wszystkim na wymaganiach
dotyczących testowanego komponentu aplikacji.
Behaviour-driven development, sformułowane po raz pierwszy przez Dana Northa w magazynie "Better Software" z marca 2006, to podejście skupiające się przede wszystkim na wymaganiach dotyczących testowanego komponentu aplikacji. Jest tak naprawdę delikatną ewolucją test-driven development. Zasadniczą różnicą jest położenie nacisku na weryfikację konkretnych wymagań stawianych aplikacji. Każdy test (scenariusz) tworzony jest w taki sposób, aby opisywać pewne zachowanie systemu. Jednym z postulatów, które pojawiły się także w przytoczonym artykule, jest nadawanie metodom testowym nazw, które de facto są zdaniami opisującymi nasze oczekiwania. W przypadku naszego testu moglibyśmy przemianować metodę na:
@Test public void shouldReceiveTwentyPercentOfDiscountIfRecipientIsJUGMember () { ... }
Sposób, w jaki piszemy testy, najczęściej powinien przebiegać według następującego schematu:
-
definiowanie warunków początkowych,
-
wykonanie logiki podlegającej weryfikacji,
-
sprawdzenie zgodności otrzymanych rezultatów z oczekiwanymi,
przekładając to na bardziej opisową konstrukcję moglibyśmy napisać:
-
mając określone warunki początkowe;
-
gdy wykonamy pewną operację w komponencie testowanym;
-
wówczas powinniśmy otrzymać oczekiwany rezultat.
Finalna wersja naszego testu, podążająca za konwencją BDD, mogłaby zatem wyglądać następująco:
@Test public void shouldReceiveTwentyPercentOfDiscountIfRecipientIsJUGMember () { // given Participant p = new Participant(); p.setJavaUserGroupMember(true); // when RegistrationService registrationService = new RegistrationService(); PaymentDetails paymentDetails = registrationService.register(participant); // then assertEquals(80.0, paymentDetails.getPrice()); }
Szablon ten bezdyskusyjnie poprawia przejrzystość testu i ułatwia jego zrozumienie. Po raz pierwszy zobaczyłem go na prezentacji Szczepana Fabera podczas konferencji GeeCON. Jest to jednak tylko konwencja, która bez dyscypliny w zespole może zostać bardzo szybko zapomniana. Na szczęście istnieją biblioteki, które wspierają technikę behaviour-driven development i pozwalają nam myśleć o testach jak o realnych wymaganiach stawianych systemowi. Jedną z nich jest właśnie easyb.
Czym jest easyb?
Easyb to zrealizowany w Groovym język dedykowany (ang. DSL; domain-specific language), oferujący konstrukcje promowane przez BDD. Podstawowymi pojęciami są tu scenariusze (ang. scenario) oraz historie (ang. story), będące zbiorami scenariuszy. Spójrzmy ponownie na nasz test i spróbujmy określić wymaganie, które stawiamy modułowi do rejestracji uczestników:
Użytkownicy mający członkostwo w Java User Group powinni otrzymać 20% zniżki podczas rejestracji.
-
mając użytkownika z członkostwem w JUG;
-
gdy zarejestruje się on na konferencję;
-
wówczas powinien otrzymać 20% zniżki.
W easyb nasz test (scenariusz) wyglądać będzie następująco:
easyb to tak naprawdę Groovy
scenario "Java User Group members should receive 20% discount", { given "participant is a JUG member" when "he registers for the conference" then "he should receive 20% discount" }
Ponieważ easyb to tak naprawdę Groovy, możemy korzystać z dowolnych klas napisanych w języku Java, w tym także je testować. Drugą istotną zaletą tego języka są domknięcia (ang. closure), dzięki którym nasz kod staje się bardziej zwięzły, a scenariusz przedstawiony powyżej wykonywalną dokumentacją. Ostateczna postać naszego scenariusza mogłaby zatem mieć taką formę:
scenario "Java User Group members should receive 20% discount", { given "participant is a JUG member", { p = new Participant() p.setJavaUserGroupMember(true) } when "he registers for the conference", { registrationService = new RegistrationService() paymentDetails = registrationService.register(participant) } then "he should receive 20% discount", { paymentDetails.price.shouldBe 80.0 } }
Co ciekawe, test w niezaimplementowej formie jest również wykonywalny. W raporcie stworzonym przez easyb figurował będzie jako "w toku" (ang. pending).
Running user registration story (UserRegistrationStory.groovy) Scenarios run: 1, Failures: 0, Pending: 1, Time elapsed: 0.89 sec 1 behavior ran (including 1 pending behavior) with no failures
Kolejną ciekawą cechą easyb są metody weryfikacji otrzymanych danych, przypominające opis w języku naturalnym. Porównując standardowe podejście
assertEquals(80.0, paymentDetails.getPrice());
z tym, oferowanym przez easyb nie trzeba chyba nikogo przekonywać, które jest bardziej zrozumiałe:
paymentDetails.price.shouldBe 80.0
Easyb oferuje całą gamę metod do sprawdzania wyników scenariuszy. I tak do porównania dwóch obiektów mamy następujące wyrażenia:
shouldBe / shouldNotBe shouldEqual / shouldNotEqual
możemy porównywać ich wartości:
shouldBeGreaterThan shouldBeLessThan shouldStartWith shouldEndWith
jak również weryfikować ich typy:
shouldBeA / shouldNotBeA shouldBeAn / shouldNotBeAn
nie może także zabraknąć wygodnych metod do sprawdzania zawartości kolekcji:
shouldHave / shouldNotHave
Byłoby dużą niesprawiedliwością i nadużyciem przemilczenie istnienia takich bibliotek javowych jak Hamcrest czy FEST-Assert, które również oferują zbliżoną do języka naturalnego weryfikację wyników testu.
Dodatkowe zalety
Jak już wspomniałem wcześniej, testy pisane w easyb mają tak naprawdę formę wykonywalnej, choć dość uproszczonej, dokumentacji. Stąd wykonując scenariusze, możemy na ich podstawie generować raporty, które traktować możemy jako zręby dokumentacji systemu. W tym momencie oferowane są nam dwie opcje – prosta forma tekstowa oraz raport w postaci strony HTML. Poza informacjami zilustrowanymi rysunkiem 1 i 2, zawierać one mogą także szczegółowe informacje o błędach, które pojawiły się podczas wykonywania scenariuszy.
Rys 1. Podsumowanie scenariuszy w postaci strony HTML
Testy pisane w easyb
mają tak naprawdę formę wykonywalnej,
choć dość uproszczonej, dokumentacji.
Rys 2. Szczegóły scenariusza będącego w "toku"
1 scenario executed successfully. Story: Users registration scenario Java User Group members should receive 20% discount given participant is a JUG member when he registers for the conference then he should receive 20% discount
Scenariusze tworzone w easyb cechuje prosta i zrozumiała struktura. Pokusić by się można zatem o zaangażowanie do ich tworzenia członków projektu, którzy niekoniecznie mają na co dzień do czynienia z kodem. W dobie narzędzi takich jak FitNesse czy Concordion wydaje się jednak mało prawdopodobnym, aby osoby nietechniczne chętne były do opisywania wymagań w easyb. Od pewnego czasu spotkać jednak można pogłoski o przygotowywaniu aplikacji Easiness, która ma wyjść na przeciw oczekiwaniom takich użytkowników.
Integracja
Bez wsparcia dla narzędzi takich jak IDE czy serwer ciągłej integracji nie możemy w pełni wykorzystać zalet, jakie oferują nam solidne testy. W momencie oddawania tego artykułu dla easyb dostępna jest wtyczka do IntelliJ IDEA oraz, od niedawna, do Eclipse. Poza tym możemy uruchomić scenariusze napisane w easyb za pomocą Apache Ant, Maven bądź z linii komend.
Oczywiście easyb to nie jedyne narzędzie
dla JVM do tworzenia testów w oparciu o
behaviour-driven development.
Alternatywy
Oczywiście easyb to nie jedyne narzędzie dla JVM do tworzenia testów w oparciu o behaviour-driven development. Oto niektóre z nich:
Konkluzja
W artykule tym zaprezentowana została ciekawa alternatywa pisania testów, jaką jest behaviour-driven development, a w szczególności easyb. Pozwala ona koncentrować się na wymaganiach, a testy tworzyć w taki sposób, aby zrozumiałe były także dla osób bez doświadczenia informatycznego. Oczywiście możliwości i sposoby implementowania scenariuszy w easyb są dużo szersze. W artykule tym przedstawione zostały jedynie najistotniejsze jego cechy i funkcjonalności. Gorąco zachęcam do odwiedzenia stron podanych w sekcji Źródła, ale przede wszystkim do poeksperymentowania. Bardzo dobrym zastosowaniem, szczególnie uwydatniającym zalety easyb, są testy funkcjonalne interfejsu użytkownika. Opis interakcji użytkownika z naszą aplikacją, dzięki scenariuszom, wygląda bardzo naturalnie. W tandemie z wygodną biblioteką emulującą zachowanie użytkownika w przeglądarce tworzy naprawdę ciekawe rozwiązanie, ale to temat na kolejny artykuł.
Bardzo dobrym zastosowaniem,
szczególnie uwydatniającym zalety easyb,
są testy funkcjonalne interfejsu użytkownika.
Źródła
-
http://dannorth.net/introducing-bdd - wprowadzenie do behaviour-driven development
-
http://easyb.org/ - strona główna projektu easyb
-
http://parleys.com/display/PARLEYS/Home#talk=28573704 – prezentacja o easyb z konferencji Devoxx
-
http://fitnesse.org/ - strona projektu FitNesse
-
http://code.google.com/p/givwenzen/ - strona projektu GivWenZen
-
http://www.concordion.org/ - strona projektu Concordion
-
http://code.google.com/p/specs/ - strona projektu Specs
-
http://groovy.codehaus.org/Using+GSpec+with+Groovy – wprowadzenie do Spec
-
http://jbehave.org/ - projekt JBehave
-
http://wiki.github.com/aslakhellesoy/cuke4duke - strona projektu Cuke4Duke
-
http://code.google.com/p/hamcrest/ - biblioteka Hamcrest
-
http://fest.easytesting.org/assert/wiki - biblioteka FEST-Assert
Nie ma jeszcze komentarzy.