BDoc - Supporting Behavior-Driven Development

Java Development News:

BDoc - Supporting Behavior-Driven Development

By Per Otto Bergum Christensen

01 Oct 2008 | TheServerSide.com

Getting the words right is always important. In the context of unit testing it would be nice if a test says something useful, and to a broad audience. Behavior-driven development (BDD) with its practices gives guidelines for how this can be achieved, making use of the ubiquitous language when writing tests.

BDoc documents behavior specified by unit tests and adapts the conventions of BDD. This is done while still supporting the standard way of writing unit tests. BDoc is installed into a project as a Maven2 plug-in and supports JUnit3, JUnit4 and TestNG.

The basic approach to broaden the audience of unit tests is to convert the test method name to sentences and print a report. This was already done in 2003, by TestDox. BDoc takes this basic idea further by adding support for different styles of examples found in BDD, with the possibility of adding references between user stories and tests. Mixing user stories into the tests might sound like a strange idea, but it is actually fun and requirements are often given on this form anyway.

Take a look at the following test class:

@Ref(Story.TASKTRACKING)
public class TestExecutiveOfficer {

 private ExecutiveOfficer bob;

 @Before
 public void setupExecutiveOfficer() {
 bob = new ExecutiveOfficer("Bob");
 }

 @Test // Scenario - given[]When[]Then[] 
 public void            
   givenAnOfficerWithNoTasksWhenTheOfficerCreatesANewTaskThenEnsureItIsAssignedToTheOfficer(){
 Task task = bob.createTask("Register salesorder");
 assertTrue(bob.isAssignedTo(task));
 }
 
 @Test // Specification - should[]
 public void shouldBeAbleToStartATaskAssigned() {
 Task task = bob.createTask("Register salesorder");
 bob.start(task);
 assertTrue(task.isInProgress());
 }

 @Test // Statement - []
 public void aNewExecutiveOfficerShouldNotHaveAnyTasksInHisOrHerTaskList() {
 assertTrue(bob.getTaskList().getList().isEmpty());
 }
}

The example above contains all the elements that BDoc will handle:

  • Story reference (@Ref(Story.TASKTRACKING))
  • Test written as an example of type scenario (public void givenAnOfficer...)
  • Test written as an example of type specification (public void shouldBeAble...)
  • Test written as statement (public void aNewExecutiveOfficer...)

The resulting bdoc is shown below:

Tasktracking demo

As an executive officer,
I want to manage tasks,
so that I can keep track

Scenarios 

  • Given an officer with no tasks
  • When the officer creates a new task
  • Then ensure it is assigned to the officer

Specifications 

  • ExecutiveOfficer
    • Should be able to start a task assigned

Statements 

  • ExecutiveOfficer
    • A new executive officer should not have any tasks in his or her task list

The Doc

BDoc does not require tests to be written as scenarios, specifications, or be referenced by user stories. To get a feel of what your unit tests communicates it's only necessary to install the plug-in and run mvn bdoc:doc.

After a first run of BDoc the result could be:

com.googlecode.bdoc.examples.account

Statements 

  • Account
    • Case 1
    • Case 2
    • Case 3

A reader of this could probably tell that Account supports three different cases. You would need to look at the test code to understand what an Account is about, or the actual source. This is good case for BDoc, to make sentences out of the test method names. After renaming the tests and a second run of BDoc the result could be:

om.googlecode.bdoc.examples.account

Specifications 

  • Account
    • Should add deposit to balance
    • Should withdraw amount from balance
    • Should not be overdrawn

Value is added to the generated report just by giving some attention to how a test is named. Note that we now have used three specifications. Writing a test as a specification is often better than writing it as a statement. A statement could by anything. A specification leads the developer to specify behaviour where the result can easily be asserted.

If your project has user stories as input BDoc can take the generated bdoc one step further. A user story is described with a title, a narrative and a series of scenarios. BDoc lets the developers define this in the code and reference it from the tests. The stories are defined in an enumeration:

public enum Story {

 DEPOSIT_AND_WITHDRAW_FUNDS( 1, 
         "As an Account Holder", 
         "I want to deposit funds to an Account",
    "So that I can withdraw them later");

 private Integer id;
 private String[] narrative;

 Story(Integer id, String... narrative) {
  this.id = id;
  this.narrative = narrative;
 }

 public Integer id() {
  return id;
 }

 public String[] narrative() {
  return narrative;
 }
}

By referencing a story from a test, the behaviour specified will be associated with that story. We could also add a test that is a scenario, a broader example compared to a specification:

@Ref(Story.DEPOSIT_AND_WITHDRAW_FUNDS)
public class TestAccount {

 @Test //Scenario
 public void givenIHave200$InMyAccountWhenIAskToWithdraw20$ThenIShouldBeGiven20$AndMyBalanceShouldBe180$() {
  Account account = new Account(200);
  int cashGiven = account.withdraw(20);
  assertEquals(20, cashGiven);
  assertEquals(180, account.balance());
 }

 @Test
 public void shouldAddDepositToBalance() {
  Account account = new Account(0);
  account.deposit(100);
  assertEquals(100, account.balance());
 }

 @Test
 public void shouldWithdrawAmoutFromBalance() {
  Account account = new Account(100);
  account.withdraw(20);
  assertEquals(80, account.balance());

 }

 @Test(expected=IllegalStateException.class)
 public void shouldNotBeOverdrawn() {
  Account account = new Account(20);
  account.withdraw(21);
 }
}

A user story and a scenario have been added. A third run of BDoc could be:

Deposit and withdraw funds

As an Account Holder,
I want to deposit funds to an Account,
So that I can withdraw them later

Scenarios 

  • Given I have 200$ in my account
  • When I ask to withdraw 20$
  • Then I should be given 20$ and my balance should be 180$

Specifications 

  • Account
    • Should add deposit to balance
    • Should withdraw amout from balance
    • Should not be overdrawn

This example is doing behaviour-driven development kind of backwards, but developers will soon get the idea and do it the right way.

The diff 

BDoc also comes with a cool feature that makes a diff report between to bdocs. Each time a bdoc report is made, a copy of the report is saved. Default behaviour of the diff command is to compare the two latest bdocs. A diff run on the Account example will print the following report:

com.googlecode.bdoc.examples.account

  • New specifications
    • Account
      • Should add deposit to balance
      • Should withdraw amout from balance
      • Should not be overdrawn
  • Deleted statements
    • Account
      • Case 1
      • Case 2
      • Case 3

A diff report that runs on the build server every hour, presented on a team screen, could be a cool way of communicating changes in the code.

Summary

BDoc will document the tests in such a way that they will work as documentation. It will encourage unit-testing, test-driven development and behaviour-driven development. Testing at the unit level is essential, the tests are the barrier that will stop errors flooding your code when changes and new requirements are specified. Writing test first is important in software development and BDoc will help getting the words right.

BDoc is currently in version 0.7.8, but is mature enough to use.

For details about how to set up BDoc and use it on your project, go to http://bdoc.googlecode.com.

About the author and programmer behind BDoc

Per Otto Bergum Christensen is an JEE consultant in Norway (Oslo area). He has been developing software in Java since 2000. http://blog.perottobergumchristensen.com/