Behaviour-Driven Development with JGiven
Why should we write tests?
Many young and less experienced developers think that writing test is a waste of time, and unnecessary work. But everyone who has experience with a big/long-term project, does not share that opinion. Firstly, with a test you can ensure that your code works as it expected with given inputs, and secondly every later change of your code will be easier and safer, tests will warn developer if developer breaks a functionality.
Many times I’ve heard: “My code works, I don’t need to test it.” So, let’s suppose that is true, and put yourself in a position that you have to add new functionality in an existing complex code. Let’s suppose that code is written by someone with the same thinking, that his code works. If there are no tests, how will you know that you don’t break existing functionality? Literally you will hate that person. Or just imagine a pilot, that is overconfident and does not do all necessary checking, thinking why should he waist his time, and repeat all procedures, he knows everything is OK. Would you fly with him? I’m sure that answer is “No, he isn’t professional”. This is the same with the programmers, why should someone hire a programmer who is not professional?
What is Behaviour-Driven Development?
Behaviour-Driven Development(BDD) is an agile software development process that enhances collaboration in a software project between developers, QAs, managers and business. Also BDD can be seen as set of best practices for using common(ubiquitous) language by technical and business teams. Therefore it should be accepted by both sides. And at least it brings the following benefits for both sides:
- Clean and transparent collaboration – by using the common language understood by all participants in project we reduce misconceptions and misunderstanding. Technical teams has better understanding what should be done, on other hand business teams have better overview about progress.
- Software design follows business values – BDD puts accent on business values and processes, and it focuses on the business behaviour more than on implementation itself.
- Developer confidence – team that use BDD are more confident that understand requirements, and because of the same language in test, team is sure that it has implemented the story correctly.
Writing user stories
Every development of a feature starts with the feature specification. And because of that, in BDD is very important that feature user stories are written in ubiquitous language and they have following structure:
Title | The story should have a clear, explicit title |
Narrative | A short description, that contains: |
Who – the actor who derives business benefit from the story. | |
What – effect the stakeholder wants the story have. | |
Why – business value that stakeholder will derive from this effect. | |
Acceptance criteria | A description of each specific case of the narrative. Such a scenario has the following structure: |
Given – the initial condition that is assumed to be true at the beginning of the scenario. | |
When – events trigger the start of the scenario. | |
Then – expected outcome, in one or more clauses. |
Story example:
Title: Premium customer can preorder a book before official release date
Description:
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.
Acceptance criteria
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 on technical teams side
For technical teams, BDD tells teams how to write tests which show that a writen code works in all scenario cases as it’s expected. Test should express it in a natural language, so that a non-technical person can understand it. It should be close/similar to acceptance criteria from the story as much as possible. To achieve that, developers use many different testing framework (i.e. Cucumber, JBehave, etc). On my most recent project I’ve used the JGiven, which is not so popular, but it’s very good for Java developers, and I would recommend it for the following reasons:
- write scenarios in plain Java using fluent API
- small and easy to learn
- generates reports readable by business people in various formats (text, json, html)
- open source, although it’s mainly developed by German company TNG, but it’s an open source and everyone can join development
- easy integration with JavaEE, Spring, Selenium and Android(experimental status)
Get started with JGiven
To enable JGiven in our project we have to include just JGiven dependency:
- 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' }
- In case of a Spring application you need to add @EnableJGiven in the test configuration classe and include following dependencies
- 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' }
Write JGiven test
Every JGiven test are built from the following elements:
- Test class with test methods, every method represent one scenario case.
- Given classes with methods that create initial conditions.
- When classes with methods that execute scenario.
- Then classes with methods that checks conditions after execution of scenario.
Given, When, Then stages are reusable and they could be used in more than one test. That is one additional benefit of using JGiven tests.
Create Given classes
As it was already mentioned, Given class should provide methods that create initial system condition. Every Given class should extend Stage<SELF>. To make clear how to write Given class, let’s write the given class for the bookstore example:
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(); } }
All class fields that we create in this stage, and we want to use in another stages (given, when, then), we must mark with @ProvidedScenarioState. In that way we say to JGiven, to pickup this property, and inject it in another stages. Every stage could have a method that is executed just after the stage completion, this method must be mark @AfterStage. Usually we use this method to persists stage objects into DB. Also, every stage could have a method that is executed after the scenario completion, it is marked by @AfterScenario. This method we use to clean up system/db. And every stage should contain at least one stage method. Naming stage methods, doesn’t follow java convention, but that is ok, because every method call later will be converted in a text segment in test report. One more important thing: every method should return self() in order to allow fluent API.
Create When classes
Writing a When class is almost identical to Given stage, but stage methods should execute scenario(calls service, rest, do some scenario actions). Let’s write when stage for our example:
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(); } }
Here we have fields Book book and Customer customer, both fields are marked with @ExpectedScenarioState. This annotation tells to JGiven framework to populate those fields (similar to @Inject) with values that are provided/created(@ProvidedScenarioState) in some previous stages. Here we have one annotated method @BeforeStage, this method will be executed first, similar to JUnit @Before, usually we initialize some class property, in this case our OrderService.
Create Then classes
Then class stages should do assertion of system, and writing these classes are very similar to Given and When. Let’s write then stage for our example:
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(); } }
Create Test classes
Every test class extends ScenarioTest<GIVEN,WHEN,THEN>. By extending this class, the class inherits methods: given(), when(), then() that represent default test stages. Test class should contain methods, each method test one scenario case. Method name of class should be similar to title of scenario case from acceptance criteria. To make things more clear, lets analyze test class for our example:
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... }
As it’s mentioned earlier every test has three default stages, in our cases: GivenBook, WhenOrder, ThenLibrary. But in many cases, these three stages are not enough for write all scenarios. Because of that JGiven provide us @ScenarioStage, which works similar like Spring @Autowire or @Inject, and set stage property. In our case we suppose that we have implemented GivenCustomer already. JGiven will set that property, and we can use it in test. In test method, we are calling first methods of given stages in order to crate initial conditions, then we call methods of when stages and last methods of then stage. If you take a look to code of test method, code is very similar to AC from the story and it’s easy to read and detect why test fails if it fails. It’s worth to mention that we can have more than one when/then stage. Let’s imagine that in our case we want to check that an email notification has been sent to customer, we would introduce i.e. ThanEmail stage.
Also in the example we use custom @Story that extends @IsTag annotation, it allows us to do easy categorization of test. It could be very useful in reports. We will talk about it in the next section.
Reporting
Reporting are very important for both sides, technical and business. Technical teams, will use report during development, and later to find issues if they exist. By default JGiven generates plain text report, and it is shown in console(of IDE). In our example if we run method written above, in console of our IDE we will get following result:
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
Beside the plain text reports, JGiven has possibility to generate HTML reports. This kind of report is very useful because it could be integrated with an CLI(i.e. Jenkins). And business teams, can easy track progress and status of project. For easier tracking of story, use cases, etc JGiven provide us annotation @IsTag, that allows easier classification and searching reports by them. Here is a screen shoot of an HTML report:
HTML report example – If you are interested in to see how HTML report works and looks, you can see and play with it on JGiven’s own tests.
So, what’s next?
If you want to make your organization more professional, improve your development process and raise the quality of your product, you should try BDD and JGiven. More about JGiven you will find on JGiven page.