Nedávno se mi do RSS čtečky dostal odkaz na novou verzi JBehave 3.0 – BDD nástroje, který poskytuje zajímavé možnosti zejména při testování vašeho kódu. Pojďme se tedy letmo seznámit s tímto zajímavým projektem a ukažme si na příkladech, jak ho použít.

BDD

Pokud vám pojem BDD – Behavior Driven Development nic neříká, pak vězte, že se jedná o agilní techniku programování, která vznikla jako reakce na TDD (test driven development) v roce 2003. Zaměřuje se především na jasné pochopení toho, co váš software dělá a jakým způsobem se chová. Navíc se snaží klást důraz na to, aby testy, které napíšete byly srozumitelné všem, bez rozdílu toho jestli se jedná o programátora či nikoliv. Tolik asi strohý oficiální úvod – více naleznete například zde.

My se v našem článku zaměříme především na zmiňované pochopení testovacích scénářů i ne-programátory. Tato myšlenka je totiž natolik smělá a přitom velmi užitečná, že stojí za to ji prověřit. Nástrojů, které implementují tuto techniku je celkem mnoho a existují pro mnoho programovacích jazyků – jedním z nich je i JBehave.

JBehave

JBehave je v podstatě stejně staré jako samotné BDD – jeho historie tedy sahá do roku 2003.  Významnějšího vzkříšení se mu ale dostalo až v roce 2008 s jeho druhou verzí. Samotný JBehave má několik modulů – například s podporou pro Spring, či Maven. Navíc existuje i projekt JBehave Web Runner, což je webová aplikace, která umožňuje spouštění jednotlivých scénářů přes webové rozhraní. To ještě více přibližuje následné testování neprogramujícím členům vašeho teamu.

Dost bylo teoretického úvodu, podíváme se na to, jak se JBehave používá. Začneme s Maven projektem, do kterého přidáme následující závislost:


<dependency>
<groupid>org.jbehave</groupid>
<artifactid>jbehave-core</artifactid>
<version>3.0</version>
</dependency>

Poté již můžeme začít používat JBehave v plné jeho síle. Předtím si ovšem ještě popíšeme jeho základní stavební kameny:

  • steps – jak je z názvu jasné, jsou to jednotlivé kroky, z kterých se posléze bude skládat příběh
  • stories – příběhy, reprezentují spojené kroky (v podstatě testovací scénáře vytvořené z jednotlivých kroků)
  • convertors – umožňují převádět textovou reprezentaci objektů zpět na objekty

JBehave – vytváření kroků

K tomu abychom mohli vytvářet kroky musíme mít nejprve nějakou třídu k testování. Pro naše potřeby zvolíme opět co nejjednodušší třídu Article (pro jednoduchost bez get a set metod):


public class Article {
private Long id;
private String name;
private String content;
private Category category;
private Author author;
...
}

Samozřejmě, že zde ještě potřebujeme třídy Category a Author, ale ty jsou tak jednoduché, že je určitě dokážete odvodit.

Nyní přistoupíme k samotnému vytváření kroků testu – nejprve si uvedeme příklad a poté si jednotlivé prvky popíšeme:


public class ArticleSteps {

private Article article;

@Given("clanek jmenem $name a obsahem: $content")
public void article(String name, String content) {
article = new Article(name, content);
}

@When("clanek je v kategorii $category")
@Alias("kategorie clanku je $category")
public void articleInCategory(Category category) {
article.setCategory(category);
}

@When("autorem clanku je $author")
@Aliases(values = {"autor clanku je $author", "$author je autorem clanku"})
public void articleAuthored(Author author) {
article.setAuthor(author);
}

@Then("clanek musi byt v kategorii $category a jeho autorem je $author")
public void articleAuthorAndCategoryMustBe(@Named("category") Category category, @Named("author") Author author) {
Assert.assertTrue(category.getName().equals(article.getCategory().getName()));
Assert.assertTrue(author.getName().equals(article.getAuthor().getName()));
}

@Then("clanek musi byt v kategorii $category")
public void articleCategoryMustBe(Category category) {
Assert.assertTrue(category.getName().equals(article.getCategory().getName()));
}

@Then("autorem clanku musi byt $author")
public void articleAuthorMustBe(Author author) {
Assert.assertTrue(author.getName().equals(article.getAuthor().getName()));
}
}

Jak si můžete všimnout, JBehave se konfiguruje kompletně pomocí anotací, což nám umožňuje efektivně definovat jednotlivé kroky, na druhou stranu při hodně krocích bychom se brzy mohli dostat do zmatku anotací. Jednotlivé metody pak odpovídají jednotlivým krokům, které reprezentují samotné testy. Tyto metody jsou anotovány následujícími anotacemi:

  • @Given – znamená přípravu na samotný test (reprezentuje definici objektů, které budeme testovat – v našem případě se jedná o vytvoření článku, který má nějaké jméno a nějaký obsah)
  • @When – definuje podmínku testu (reprezentuje tedy definici prostředí, v kterém se bude test provádět – zde máme dvě metody; první nastavuje článku nějakou kategorii a druhá pak autora – zde bude využito i convertorů, které si popíšeme dále)
  • @Then – definuje samotný test (reprezentuje tedy stav, který chceme otestovat – máme zde tři testy – vždy na obsah konkrétního atributu článku)

Každá z předchozích anotací má jeden parametr – ten určuje pattern, podle kterého bude rozpoznán tento krok v celkovém příběhu (story). Při vyhledávání jednotlivých kroků se postupuje vždy od složitějšího k jednoduššímu, takže pokud máte podobný začátek patternu záleží pak na pořadí metod v třídě kroků.

Pattern může obsahovat parametry, které jsou vždy uvozeny znakem $. Parametry jsou pak dosazeny do stejnojmenných parametrů metod, či do parametrů, které jsou uvozeny anotací @Named (zdej jsem objevil jednu záludnost – pokud chceme pojmenovat pouze jeden parametr pomocí této anotace a druhý parametr metody necháme bez ní, pak JBehave vyhodí NPE, protože nebude schopen rozpoznat tento druhý parametr – musíte tedy v případě více parametrů v metodě oanotovat všechny, nebo žádný).

Každá metoda může mít hned několik aliasů, které jsou alternativou k hlavnímu patternu a které se definují anotacemi @Alias pro jeden alias, respektive @Aliases pro více aliasů.

JBehave – convertory

Pro to, abychom mohli používat naše objekty přímo v metodách jednotlivých kroků, musíme zajistit, že se z textových parametrů správně převedou na objekty. K tomu slouží, stejně jako v mnoha jiných projektech, convertory. Jelikož v předchozím případě používáme objekty Category a Author, musíme vytvořit convertory pro oba. My si zde ukážeme pouze convertor pro třídu Category, druhý pak uděláte analogicky:


public class CategoryConverter implements ParameterConverters.ParameterConverter {

public boolean accept(Type type) {
if (type instanceof Class) {
return Category.class.isAssignableFrom((Class) type);
}
return false;
}

public Object convertValue(String s, Type type) {
Category category = new Category();
category.setName(s);

return category;
}
}

Jak vidíte, nejedná se o nic světoborného – v našem convertoru je třeba implementovat dvě metody. První accept určuje třídu/y, které tento convertor umí převést a druhá metoda zaručuje způsob převodu z textové reprezentace na objekt.

JBehave – stories

A blížíme se k spouštění testů. Nyní nás čeká nejzajímavější část – tedy psaní samotných scénářů (příběhů – stories). Příběh může být reprezentován pomocí textového souboru, XML a HTML. Pro jednoduchost zde uvedeme příklad scénářů vytvořených pomocí textového souboru:


Scenario:  zalozeni clanku a prirazeni kategorie a autora

Given clanek jmenem Pokus a obsahem: Pokusny clanek, ktery vytvarime pomoci story
When clanek je v kategorii Pokusna kategorie
When autorem clanku je Pan Pokus
Then clanek musi byt v kategorii Pokusna kategorie a jeho autorem je Pan Pokus

Scenario:  zalozeni clanku a prirazeni kategorie

Given clanek jmenem Pokus a obsahem: Pokusny clanek, ktery vytvarime pomoci story
When kategorie clanku je Pokusna kategorie
Then clanek musi byt v kategorii Pokusna kategorie

Scenario:  zalozeni clanku a prirazeni autora

Given clanek jmenem Pokus a obsahem: Pokusny clanek, ktery vytvarime pomoci story
When Pan Pokus je autorem clanku
Then autorem clanku musi byt Pan Pokus

V našem souboru jsme nadefinovali tři příběhy (scénáře), které chceme testovat. Zde se ukazuje síla BDD – pokud nadefinujeme jasnou terminologii, testy může psát i člověk, který o programování neví zhola nic. Poté co testy takto napíše je může předat ke spuštění, nebo si je sám spustit pomocí nástroje JBehave Web Runner. Jednotlivé řádky jsou vždy uvozeny jménem anotace a o oddělení příběhů se stará řádek začínající na Scenario. V jednotlivých scénářích jsem použil i aliasy tak, aby bylo vidět, že není problém zaměnit určitá slova.

JBehave – spouštění stories

Spouštět příběhy (stories) můžeme několika způsoby. Prvním z nich je spuštění přímo z jiné Java třídy – vytvoříme tedy následující třídu, která obsahuje logiku pro načítání námi zadaných scénářů (v textové podobě):


@RunWith(AnnotatedEmbedderRunner.class)
@Configure(storyLoader = JBehaveTest.MyStoryLoader.class, storyReporterBuilder = JBehaveTest.MyReportBuilder.class,
parameterConverters = {AuthorConverter.class, CategoryConverter.class, ArticleConverter.class})
@UsingEmbedder(embedder = Embedder.class, generateViewAfterStories = true, ignoreFailureInStories = true, ignoreFailureInView = true)
@UsingSteps(instances = {ArticleSteps.class})
public class JBehaveTest implements Embeddable {

private Embedder embedder;

public void useEmbedder(Embedder embedder) {
this.embedder = embedder;
}

@Test
public void run() {
embedder.runStoriesAsPaths(new StoryFinder().findPaths(codeLocationFromClass(this.getClass()).getFile(), Arrays.asList("**/*.story"), Arrays.asList("")));
}

public static class MyStoryLoader extends LoadFromClasspath {
public MyStoryLoader() {
super(JBehaveTest.class.getClassLoader());
}
}

public static class MyReportBuilder extends StoryReporterBuilder {
public MyReportBuilder() {
this.withFormats(CONSOLE, TXT, HTML, XML).withDefaultFormats();
}
}

}

Všimněte si, že kromě celkem jednoduchých metod, v kterých definujeme kde má JBehave hledat naše scénáře, je zde také poměrně složitá anotační hlavička. V  té nastavujeme jednak convertory, které budou určeny k převodu našich objektů a následně také například výstupy, do kterých se má vygenerovat výsledek našeho testu.

Ačkoliv je první způsob spouštění scénářů poměrně elegantní, přeci jenom se nedá srovnat s jednoduchostí, kterou nám poskytuje Maven plugin. Ten využívá třídy, které rozšiřují třídu JUnitStories a definují, odkud se mají scénáře načítat. Nejprve je tedy třeba vytvořit tuto třídu a říct jí, kde má hledat námi definované scénáře:


public class ArticleStories extends JUnitStories {

public ArticleStories() {
}

@Override
protected List storyPaths() {
String codeLocation = codeLocationFromClass(this.getClass()).getFile();
return new StoryFinder().findPaths(codeLocation, Arrays.asList("*/**/article.story"),
Arrays.asList(""), "file:" + codeLocation);
}
}

Následně pak stačí do Maven konfigurace vložit tuto definici pluginu a předat mu názvy našich tříd, které jsme vytvořili v předchozím kroku. Všechny testy jsou pak spuštěny v test fázi buildu:


<plugin>
<groupid>org.jbehave</groupid>
<artifactid>jbehave-maven-plugin</artifactid>
<version>3.0</version>
<executions>
<execution>
<id>run-stories-as-embeddables</id>
<phase>integration-test</phase>
<configuration>
<includes>
<include>**/*Stories.java</include>
</includes>
</configuration>
<goals>
<goal>run-stories-as-embeddables</goal>
</goals>
</execution>
</executions>
</plugin>

Závěrem

Jak sami vidíte, JBehave nám poskytuje zajímavé možnosti pro testování našich aplikací. Po vytvoření základní infrastruktury umožňuji psaní testů i neprogramátorům a tím zjednodušuje složitou fázi testování software.

Tento typ testování s sebou nese také určité problémy – například nutnost používat jasně danou terminologii, jinak není možné testy psát. Navíc se jedná o úplně jiný přístup k testování, takže pro něj musíte získat podporu u vašich vývojářů a používat nástroje, které umožní lepší integraci s vašimi stávajícími systémy (existuje podpora pro Eclipse, Sellenium a navíc existuje již zmiňovaný JBehave Web Runner).

V tomto článku jsme nastínili pouze základní principy JBehave a BDD – pokud se chcete dozvědět více, určitě začněte na stránkách JBehave a tam najdete spoustu zdrojů k tomuto tématu (mezi nezmíněnými základními možnostmi jsou například tabulky pro opakovaná volání testů, či možnost lokalizace scénářů do více jazyků). Dejte nám také vědět do komentářů, zdali máte zkušenosti s JBehave či BDD a zdali jste se s ním setkali v praxi.

Související odkazy