Introduction to behaviour-driven development

on waitingforcode.com

Introduction to behaviour-driven development

When we are young, the pleasure of delivery features are so big that we ignore testing phase. The pleasure of writing tests comes with age when we can see that they help to maintain clean code. One of the most popular acronyms promoting tests is TDD (Test-Driven Development). However, it's not the single one because it has a complementary brother called BDD.

This article introduces the idea of Behaviour-Driven Development (BDD). At the begin we can see the main principles of this concept. The second part contains a comparison with TDD. The last part is more practical and describes a simple test case written according to BDD practice. The example is written with Cucumber.

What is BDD ?

Quick definition of BDD could be a "TDD approach enriched by business aspects". Thanks to these business aspects, BDD can be a test tool shared between technical (programmers) and non-technical people (QA or even evangelized managers). So BDD registers very well on Agile methods which underline the importance of communication between team people. BDD is also often identified as a mix of TDD and ATDD (acceptance-test driven design)

In more technical points, BDD consists on thinking about test scenarios rather than about test code. BDD tests take a form of story, such as (Java Cucumber example):

Feature: User registration

    We want gain more and more users. So they should be able to register.

    Scenario: Successful registration for valid user
        Given I specified my login
        When I want to sign in
        Then My user should be added in database

    Scenario: User should see error message when login is not specified
        Given I create user without login
        When I want to sign in
        Then My user shouldn't be added in database

    Scenario: Two users with the same logins
              We shouldn't penalize user with the same login because we want to pick up all everybody.
              We are crazy and don't care about problems caused by duplicated logins.
        Given I add users with the same logins
        When I want to sign in two identical logins: userx and userx
        Then Both users, userx and userx, should be added in database

The description can be (should be) more detailed than in that example. Thanks to this description, non-technical people can not only tell programmers what should be tested. They can also write document in the language understandable by all project participants. Between the main characteristics of BDD, we can distinguish:

  • focus on behavior - programmers focus more on "what" the code should do (which goals achieve) rather than on "how" the code should be conceived. This is more business-oriented approach and the developers are concerned not only by code but also by business logic.
  • given/when/then vocabulary - this words characterize BDD approach. As we saw in previous example, "given" introduces the context of situation. After that, "when" executes the code testing this context. At the end "then" checks if the result is correct.
  • ubiquitous language - it's used by BDD tests. This term is employed by Eric Evans to define common language between technical and non-technical people in the project. An example of this ubiquitous language can be specification described previously with the use of: "Feature:", "Scenario:", "Given", "When" and "Then" keywords.
  • bridge between XP and Scrum - ubiquitous language brings also another advantage. Thanks to it all project members are implied in software development process. So we can easily join Scrum practices (such as story telling) with eXtreme Programming ones (TDD)
  • still test-oriented first - even if "T" from TDD is replaced by "B", BDD should also always begin by defining failing test and writing the code implementation after.

BDD and TDD comparison

BDD is a kind of TDD subset. It takes the rules defined by TDD (write tests before implementation) and adapts them to Agile world (everybody implied on development process). But there are some more or less subtle differences between these two concepts:

BDD TDD
provides code acceptance criteria for final users - program final features are tested focuses on testing if every piece of code works as expected, without taking in consideration execution context
technical and non-technical people implied only programmers are concerned
story-telling format describing what given feature should do classical test format describing what given method should do
feature description files compose additional abstraction and make that BDD test are defined in higher level tests are defined in low level which is reachable only by some people (programmers)

BDD example with Cucumber

In Java, one of available BDD framework is called Cucumber. This time we won't focus on advanced options of it and only introduce some basics. To begin, we need to define files expected by Cucumber to running BDD tests:

  • feature description file - this file uses Gherkin syntax (language understandable by Cucumber) to describe what should be tested.
  • test main class - indicates which tests should be ran by Cucumber test runner.
  • steps class - uses given/when/then vocabulary to specify the code translating feature specifications to executable steps

For feature file we take the same example as in the first part of the article. It won't be duplicated here. We can pass to the declaration of test main class which is quite simple:

@RunWith(Cucumber.class)
@CucumberOptions(features= "classpath:bdd")
public class UserServiceTest {
}

@RunWith annotation is well known. It indicates which runner should be used to execute the test. After we can find @CucumberOptions annotation containing features attribute defined. This property represents the directory where feature description files are stored. In general way, @CucumberOptions helps to customize tested case.

Step class looks like:

public class UserRegistrationSteps implements En {

  private UserService userService = new UserService();

  private User user;

  public UserRegistrationSteps() {
    Before((Scenario scenario) -> {
      UserDao.deleteAll();
    });

    // 1st scenario
    Given("^I specified my login$", () -> {
      user = new User();
      user.setLogin("User_1");
      assertThat(user).isNotNull();
      assertThat(user.getLogin()).isNotEmpty();
      assertThat(UserDao.findAll()).isEmpty();
    });

    When("^I want to sign in$", () -> {
      userService.registerUser(user);
    });

    Then("^My user should be added in database",
      () -> {
          assertThat(UserDao.findAll()).hasSize(1);
          assertThat(UserDao.findAll().values()).extracting("login").containsOnly("User_1");
      });

    // 2nd scenario
    Given("^I create user without login$", () -> {
      user = new User();
      assertThat(user.getLogin()).isNull();
    });

    Then("^My user shouldn't be added in database$", () -> {
      assertThat(UserDao.findAll()).isEmpty();
    });

    // 3rd scenario
    Given("^I add users with the same logins", () -> {
    });

    When("^I want to sign in two identical logins: ([a-z]+) and ([a-z]+)", (String login1, String login2) -> {
      User user1 = new User();
      user1.setLogin(login1);
      User user2 = new User();
      user2.setLogin(login2);

      userService.registerUser(user1);
      userService.registerUser(user2);
    });

    Then("^Both users, ([a-z]+) and ([a-z]+), should be added in database", (String login1, String login2) -> {
      assertThat(UserDao.findAll()).hasSize(2);
      assertThat(UserDao.findAll().values()).extracting("login").containsExactly(login1, login1);
    }); 
  }
}

This code was written with Java 8 lambda expressions. And there are nothing special. Given/When/Then methods implement the elements defined in feature description Given/When/Then clauses. Note that the association of clauses to appropriate methods is made thanks to regular expression defined as the first parameter of these methods. Thanks to that we can even intercept some elements defined in feature description and translate them to Java objects. By the way, we do so in the 3rd scenario.

After executing this test, a output looking like that should be printed:

Feature: User registration
  
    We want gain more and more users. So they should be able to register.

  Scenario: Successful registration for valid user # bdd/user-service.feature:5
    Given I specified my login                     # UserRegistrationSteps.java:21
    When I want to sign in                         # UserRegistrationSteps.java:29
    Then My user should be added in database       # UserRegistrationSteps.java:33

  Scenario: User should see error message when login is not specified # bdd/user-service.feature:10
    Given I create user without login                                 # UserRegistrationSteps.java:40
    When I want to sign in                                            # UserRegistrationSteps.java:29
    Then My user shouldn't be added in database                       # UserRegistrationSteps.java:45

  Scenario: Two users with the same logins                        # bdd/user-service.feature:15
            We shouldn't penalize user with the same login because we want to pick up all everybody.
            We are crazy and don't care about problems caused by duplicated logins.

    Given I add users with the same logins                        # UserRegistrationSteps.java:50
    When I want to sign in two identical logins: userx and userx  # UserRegistrationSteps.java:54
    Then Both users, userx and userx, should be added in database # UserRegistrationSteps.java:64

3 Scenarios (3 passed)
9 Steps (9 passed)
0m0,147s

BDD is a good complement of TDD approach because it implies everybody concerned by developed software - programmers as well as QA and other non-technical people. It aligns well on other Agile practices because of common key point - communication. One of available frameworks to define BDD tests is Cucumber. It's based on 3 files: feautre description, test launcher and steps implementation.

Share on: