What BDD Is and Why You Need It: Java and Behavior Driven Development
This article explains what BDD is and how Behavior Driven Development helps the developer and their team to understand application logic.
Join the DZone community and get the full member experience.
Join For FreeIs Behavior Driven Development Just Another Religion?
You might hear about BDD but still don't have a clear understanding of what it is. Here I'll try to simplify the main ideas and provide clear examples. This article is useful not only for developers but all people involved in the development process like analysts, product owners, and even guys from the business.
BDD Makes Application Logic More Transparent
Low Transparency in Application is a very known problem in IT. Quite often domain knowledge belongs only to the development team. As a consequence, to get even a simple answer you have to ask the developer to explain.
Behavior Driven Development is a set of practices that makes application logic more transparent not only for developers but also for business guys, product owners, designers, etc. BDD it's not magic and won't make applications perfectly transparent for everyone: developers still have a knowledge advantage. But, this practice is very useful. Funny thing is that in practice this BDD approach helps developers the most.
Writing Simple Application as Case Study
To show how BDD can improve application transparency let's implement a simple user creation service. The design of such a service will be pretty straightforward - create/read/delete users.
public class UserService {
private final Map<String, User> users;
public UserService(){
users = new HashMap<>();
}
public User createUser(User user){
if(users.get(user.getName()) != null) throw new RuntimeException("User Exist!");
users.put(user.getName(), user);
return user;
}
public User getUser(String user){
return users.get(user);
}
}
@Data
class User{
private String name;
private String password;
private String info;
}
Implementing Tests in Old Fashion Style (No BDD Here)
For beginning let's write a couple of tests following the classical approach. Some Junit asserts for positive and negative cases.
@Test
public void userCreatedSuccessfully() {
User actual = userService.createUser(new User("John", "Smith"));
assertEquals(actual, userService.getUser("John"));
}
@Test(expected = RuntimeException.class)
public void existentUserError() {
userService.createUser(new User("John", "Smith"));
userService.createUser(new User("John", "Dorian"));
}
@Test(expected = RuntimeException.class)
public void tooShortName() {
userService.createUser(new User("ab", "Smith"));
}
For the average Java developer, you won't find anything complex. But, all other participants in the team might react like this:
Introducing BDD Practice by Adding Cucumber Tests
Now let's introduce BDD and rewrite our regular tests by Cucumber tests. The cucumber framework provides BDD support for many languages and hopefully, Java is among them. One of the fundamental things is scenarios that describe scenarios of the tests. The main purpose of such scenarios - make tests easier to read and to understand. Scenarios are described inside *.feature files.
Explaining Feature Files
Feature file has a couple of keywords provided by Cucumber like Given/When/Then. Actually, you can use them in the way you want and the framework doesn't have any restrictions. All of them are used only for one purpose - to create similar natural language scenarios. Such scenarios will be much more clear for non-developers like designers/analysts/product owners. So, let's take a look at our feature file (there is a link to source code at the end of the article):
So, in our file, we can see 4 scenarios. Examples are pretty straightforward and don't show all features given by Cucumber. But basic functionality is shown. Data from the feature file is after passing the Java test. Here you can see how it looks like:
private UserService userService;
@Given("^User API is initiated$")
public void initializeUserService() {
userService = new UserService();
}
@When("^Creating user (\\w+) with age (\\d+) calling user API$")
public void createUser(String name, int age) {
userService.createUser(new User(name, age));
}
@Then("^User Api returns (\\w+) user with (\\d+) age$")
public void validateUserByName(String name, int age) {
User userFromApi = userService.getUser(name);
Assert.assertEquals(name, userFromApi.getName());
Assert.assertEquals(age, userFromApi.getAge());
}
@Then("^Fail to create user (\\w+) with age (\\d+) when calling user API$")
public void failUserCreation(String name, int age) {
assertThrows(RuntimeException.class, () -> userService.createUser(new User(name, age)), "Should fail");
}
@When("^Creating multiple users$")
public void creatingMultipleUsers(List<User> users) {
users.forEach(user -> assertNotNull(userService.createUser(user)));
}
@Then("^All created users now available from user API$")
public void createUser(List<User> users) {
users.forEach(user -> containsInAnyOrder(users, userService.getUser(user.getName())));
}
It might look overcomplicated but actually, there is nothing complex. For each word used in Given/Then/When Cucumber finds a corresponding function in the test. Also, cucumber automatically deserializes data from feature records to method parameters.
One of the options is to write regex like statements:
Another way to write a column like records that mapped to arrays:
The source code used in this article can be cloned from here or just type:
git clone https://github.com/isicju/bdd_cucumber_tutorial.git
Advantages of the BDD Approach:
Now having an example we can say that the BDD approach makes:
- The test becomes more visual and descriptive.
- All non-developers participants are closer to application functionality.
Well, also it is worth mentioning that tools provided by Cucumber also simplify the process of test writing and maintainability (especially for tests with a big number of inputs e.g. records in databases).
Is Cucumber a Mandatory Part of BDD?
No. Cucumber provides a set of tools that follow BDD ideas. Potentially you can create your own solution and use frameworks like Spock or even Mockito for the same purpose. But, Cucumber has already become a standard in enterprise development.
Instead of a Conclusion
I strongly recommend developers add such frameworks when you write your tests. In the very end, such an approach helps developers the most. It's pretty rare when anyone else reads tests and uses them as alternatives to documentation. I expect in the future this approach will be transformed into something better.
Opinions expressed by DZone contributors are their own.
Comments