Behaviour-Driven Development sa JGiven
Zašto bi trebalo da pišemo testove?
Mnogi mladi i manje iskusni programeri misle da je pisanje testova gubljenje vremena i da predstavlja nepotreban posao. Ali svako ko ima iskustva sa velikim/dugoročnim projektima sigurno nema isto mišljenje. Prvo, sa testom se možete osigurati da vaš kod radi kako je i očekivano sa zadatim ulaznim informacijama, kao drugo, svaka sledeća promena u kodu će biti lakša i bezbednija. Testovi upozoravaju programera da li je došlo do sloma funkcionalnosti.
Mnogo puta sam imao priliku da čujem ’’Moj kod radi, nema nikakve potrebe da ga testiram.’’ Pretpostavimo da je to istina i stavite se u poziciju u kojoj treba da dodate novu funkcionalonst u postojećem kompleksnom kodu. Pretpostavimo da je kod napisao neko drugi, razmišljajući na isti način, računajući da mu kod radi. Ako ne postoje testovi, kako ćete znati da ne lomite postojeću funkcionalnost? Bukvalo ćete mrzeti tu osobu. Ili zamislite pilota, koji je toliko samouveren da ne radi sve neophodne provere, razmišljajući kako time troši vreme, ponavljajući sve procedure, kada zna da je sve OK. Da li biste leteli sa njim? Verujem da biste na to pitanje odgovorili sa ’’Ne, on nije profesionalac!’’. Isto je sa programerima – zašto bi neko zapošljavao programera koji nije profesionalan.
Šta je Behaviour-Driven Development?
Behaviour-Driver Development (BDD) je agilni softverski razvojni proces koji u softverskim projektima poboljšava kolaboraciju između programera, QA-a, menadžmenta i klijenata. BDD se takođe može videti kao skup najboljih tehnika za korišćenje univerzalnog jezika (ubiquitous) , od strane tehničkih i biznis timova. Ona treba da bude prihvaćena od obe strane. Barem donosi sledeće benefite za obe strane:
- Jasna i transparentna saradnja – korišćenjem zajedničkog jezika koji razumeju svi učesnici na projektu će smanjiti zablude i nesporazume. Tehnički timovi će bolje razumeti šta treba da se uradi, dok sa druge strane biznis timovi imaju bolji uvid u napredak projekta.
- Softverski dizajn / nacrt prati poslovne vrednosti – BDD stavlja akcenat na poslovne vrednosti i procese, i fokusira se na poslovno ponašanje više nego na samu implementaciju.
- Samopouzdanje programera – timovi koji koriste BDD su samopouzdaniji u razumevanju zahteva, pošto koriste isti jezik u testiranju, timovi su sigurni da je implementacija korisničke priče tačna.
Pisanje korisničkih priča
Svaki razvitak neke funkcionalnosti počinje sa specifikacijama funkcionalnosti. Zbog toga, BDD je veoma važno da te funkcionalnosti korisničkih priča budu napisane u jeziku koji mogu svi da razumeju i imaju sledeću strukturu:
Naslov | Priča treba da ima jasan, eksplicitan naslov |
Narativ | Kratak opis koji sadrži: |
Ko – akteri koji očekuju poslovne benefite od priče. | |
Šta – efekat koji akcionari žele da postignu pričom. | |
Zašto -poslovnu vrednost koju akcionari dobijaju od tog efekta. | |
Kriterijum prihvatljivosti | Opis svakog specifičnog slučaja u narativu. Takav scenario ima sledeću strukturu: |
Given – početni uslovi se podrazumevaju da su ispunjeni na početku scenaria. | |
When – događaji su okidači za početak scenaria. | |
Then – očekivani ishod, u jednoj ili više klauza/tačaka. |
Primer priče:
Naslov: Premium mušterija može da naruči knjigu pre datuma izdavanja.
Opis:
As a book seller In order to improve user loyalty program I want to allow premium user to be able to order books 30 days earlier.
Kriterijum prihvatljivosti
A. Premium user can order book earlier Given a book with id With official release date in 30 days And a premium customer When customer orders book Then customer library contains book with id B. Premium user cannot order book that will be released in more than 30 days. Given a book with id With official release date in 31 days And a premium customer When customer orders book Then customer gets error message C. Standard user cannot order book before official release Given a book with id With official release date in 15 days And a regular customer When customer orders book Then customer gets error message
BDD na strani tehničkih timova
Za tehničke timove, BDD govori timu kako da pišu testove koji pokazuju da napisani kod radi u svim scenarijskim slučajevima kao što je očekivano. Test treba da bude iskazan laičkim jezikom, tako da ga može razumeti osoba bez tehničkih znanja. Treba da bude što je bliže/sličnije acceptance criteria što je više moguće. Da bi to uspeo, programmer treba da koristi više različitih radnih okvira za testiranje (npr. Cucumber, JBehave, etc.). Na mom poslednjem projektu koristio sam JGiven, koji nije popularan, ali je veoma dobar za Java programere, i preporučio bih ga zbog sledećih razloga :
- Pišite scenarije isključivo u Javi koristeći fluentne API-je
- Mali su i jednostavni za učenje
- Generiše izveštaje koji su čitljivi od strane drugih ljudi uključenih u posao u različitim formatima(text, json, html)
- Open source, iako je uglavnom razvijena od strane Nemačke kompanije TNG, ali je open source i svako može da mu se priključi
- Lako se integriše sa JavaEE (experimental status)
Počeci sa JGiven
Da bi uključili JGiven u naš projekat moramo da uključimo JGiven zavisnost:
- JUnit tests:
- Maven: <dependency> <groupId>com.tngtech.jgiven</groupId> <artifactId>jgiven-junit</artifactId> <version>0.15.3</version> <scope>test</scope> </dependency> - Gradle: dependencies { testCompile 'com.tngtech.jgiven:jgiven-junit:0.15.3' }
- U slučaju Spring aplikacija treba da dodate @EnableJGiven test konfiguracione klase i da uključuje sledeće zavisnosti:
- Maven <dependency> <groupId>com.tngtech.jgiven</groupId> <artifactId>jgiven-spring</artifactId> <version>0.15.3</version> <scope>test</scope> </dependency> - Gradle: dependencies { testCompile 'com.tngtech.jgiven:jgiven-spring:0.15.3' }
Pisanje JGiven testa
Svaki JGiven test je konstruisan od sledećih elemenata:
- Test klase sa test metodama, svaka metoda predstavlja jedan slučaj u scenariju.
- Given klase sa metodama koje kreiraju inicijalne uslove.
- When klase sa metodama koje izvršavaju scenarije.
- Then Klase sa metodama koje proveravaju uslove posle izvršavanja scenarija.
Given, When, Then faze su ponovljive i mogu se koristit u više od jednog testa. To je jedan dodatni benefit korišćenja JGiven testova.
Kreairanje Given klasa
Kao što je već pomenuto, Given klase treba da pruže metode koje će kreirati inicijalne sistemske uslove. Svaka Given klasa treba da nasledi Stage<SELF>. Da bih pojasnio kako da napišete Given klasu, hajde da napišemo klasu za naš primer prethodni primer koji se ticao kupovine knjiga.
public class GivenBook extends Stage<GivenBook> { @ProvidedScenarioState protected Book book; @AfterStage private void persistBookInDb() { BookDao.save(book); } @AfterScenario private void cleanUpDb() { BookDao.delete(book); } public GivenBook a_book_with_id( String id ) { book = new Book(id); return self(); } public GivenBook official_release_in_$_days( int days) { book.releaseDate( today.add(days) ); return self(); } }
Sva klasna polja koja napravimo u ovoj fazi, a koje želimo da koristimo u drugim fazama (given, when, then), moramo obeležiti sa @ProvidedScenarioState. Na taj način govorimo JGiven-u da pokupi svojstvo i ubaci ga u drugu fazu. Svaka faza može da ima metodu koja je izvedena odmah nakon što je faza završena, ovaj metod mora biti označen @AfterStage. Uglavnom koristimo ovu metodu da bi zadržali fazne objekte u data bazi. Takođe, svaka faza bi trebala da ima metodu koja je izvršena nakon što je scenario završen, i to obeležavamo sa @AfterScenario. Ovom metodu koristimo za čišćenje sistema/data baza. Svaka faza bi trebala da ima barem jednu faznu metodu.. Imenovanje faznih metoda ne prati uobičajne Java konvencije, ali to je u redu, zato što će svaka nazvana metoda kasnije biti pretvorena u tekt u izveštaju testa. Još jedna važna stvar: svaka metodu bi trebalo return self() da bi omogućili fluentan API.
Kreiranje When klase
Pisanje When klase je gotovo identično Given fazi, ali fazne metode bi trebale da izvršavaju scenario(calls service, rest,etc.). Hajde da napišemo fazu za naš primer:
public class WhenOrderService extends Stage<WhenOrderService> { @ExpectedScenarioState protected Book book; @ExpectedScenarioState protected Customer customer; protected OrderService orderService; @BeforeStage private void init() { orderService = new OrderService(); } public WhenOrderService the_customer_order_the_book() { orderService.order(customer, book); return self(); } }
Ovde imamo polja Book book i Customer customer, oba polja su označena sa @ExpectedScenarioState. Ova anotacija govori JGiven radnom okviru da popuni ta polja (similar to @Inject) sa vrednostima koje je pružio/napravio(@ProvidedScenarioState) u nekim od prethodnih faza. Ovde imamo jednu metodu anotacije @BeforeStage, ova metoda se prva izvršava, nalik JUnit @Before, obično pokrećemo neko klasno svojstvo, u ovom slučaju naš OrderService.
Kreiranje Then klasa
Then faza bi trebala da pruži pretpostavku o sistemu, i pisanje ovih klasa je veoma slično Given i When. Hajde da napišemo ove faze za naš primer:
public class ThenLibrary extends Stage<ThenLibrary> { @ExpectedScenarioState protected Customer customer; public ThenLibrary the_library_contains_book_with_id(String bookId) { assertThat( LibraryDao.exists(customer, bookId) ).isTrue(); return self(); } }
Kreiranje test klase
Svaka test klasa nasleđuje ScenarioTest<GIVEN,WHEN,THEN>. Nasleđivanjem ove klase, klasa dobija metode: given(), when(), then() koje predstavljaju default test faze. Test klasa treba da sadrži metode, svaka metoda treba da testira jedan scenarijski slučaj. Naziv metode neke klase treba da bude sličan nazivu scenarijskog slučaja za kriterijum prihvatljivosti. Da bih razjasnio neke stvari, hajde da analiziramo test klasu za nas primer:
public class PreOrderBookScenarioTest extends ScenarioTest<GivenBook, WhenOrder, ThenLibrary> { @ScenarioStage private GivenCustomer givenCustomer; @Test @Story("Story-555333") public void premium_customer_can_order_book_earlier() { final String bookId = "123"; given().a_book_with_id( bookId ) .with().official_release_in_$_days(30); givenCustomer.and().a_premium_customer(); when().the_customer_order_the_book(); then().the_library_contains_book_with_id(bookId); } //Implementation of 2 other AC cases... }
Kao što sam spomenuo ranije, svaki test ima tri default faze: GivenBook, WhenOrder, ThenLibrary. Ali u mnogim slučajevima, ove tri faze nisu dovoljne da se napiše svaki scenario. Zato nam JGiven omogućava @ScenarioStage, koji funkcioniše slično kao Spring @Autowire or @Inject, i postavlja svojstvo faze. Pretpostavimo u našem slučaju da smo već implementirali GivenCustomer. JGiven će postaviti svojstvo koje možemo koristiti u testu. U test metodi, pozivamo prvu metodu iz Given faze kako bi kreirali inicijalne uslove. Ako pogledate u kod za test metodu, kod je veoma sličan kriterijumima prihvatljivosti iz price i lako je čitati i uočiti zašto test prolazi ili ne prolazi. Vredi napomenuti da možemo da imamo više od jedne when/then fate. Zamislimo da u našem slučaje želimo da proverimo da li je notifikacija za email poslata kupcu, uvodimo ThenEmail fazu.
U našem slučaju koristimo custom @Story koja nasleđuje @IsTag anotaciju, što nam omogućuje laku kategorizaciju testa. To može da se pokaže kao vrlo korisnim u izveštajima. Nešto više o tome u sledećem delu.
Izveštaji
Izveštavanje je veoma važno za obe strane, tehničku i poslovnuTehnički timovi će koristiti izveštaje tokom programiranja, a kasnije naći probleme ako postoje. JGiven sam generiše razumljiv tekstualni izveštaj, koji se prikazuje u konzoli(od IDE).U našem primeru, ako pokrenemo metodu napisanu gore, u konzoli našeg IDE naći ćemo sledeći izveštaj:
Scenario: premium customer can order book earlier Given a book with id 123 With official release in 30 days And a premium customer When the customer order the book Then the library contains book with id 123
Osim lako razumljivih tekstualnih izveštaja, JGiven ima mogućnost da generiše i HTML izveštaje. Ovakav izveštaj je dosta koristan jer može da se integriše sa CLI (i.e. Jenkins). Poslovni timovi mogu lako da prate napredak i trenutno stanje projekta. Za lakše praćenje priče, slučajeva, etc JGiven daje nam anotaciju @IsTag, koja omogućava lakšu klasifikaciju i pretragu izveštaja . Evo skrinšot jednog takvog HTML izveštaja:
Primer HTML izveštaja – Ako vas interesuje da vidite kako HTML izveštaj radi i izgleda, možete pogledati i igrati se sa njim na JGiven testovima.
Šta je sledeće?
Ako želite da učinite svoju kompaniju profesionalnijom, unapredite vaš programerski process i želite da podignete kvalitet svog proizvoda, treba da isprobate BDD i JGiven. Više o JGiven možete naći JGiven stranici.