Behavior-Driven Development (BDD) – The What, Why, and How

by Max Slabyak

The standard development method they teach you in Software Engineering 101 is

  1. Get your requirements
  2. Come up with a top-bottom architectural design
  3. Code up modules one by one
  4. Test (Unit/System/Regression)
  5. Delivery/Deployment

It’s a great approach and the solution you deliver can be built without compilation errors and work fine… or so you think.  You deliver the solution and all of the sudden, the client points out that there were requirements missed and/or incomplete.  My favorite taught-myself-in-24-hours-developer reaction to this is:

“But… it… compiles… therefore… it works…  it has to!”

With Behavior-Driven Development (BDD), and one of the reasons this approach is favored by QA teams all over the world is this approach ensures and, actually, requires that you work against the user requirements when working on the design.

Let’s say one of the requirements of an HR system being built is that the payroll must be able to use one pay rate when an employee has worked 40 hours or less and a different pay rate for overtime (> 40 hours).

With the top-down approach, you would eventually get down to developing this module, perhaps after the database development is done.  You also wouldn’t really be able to test it until the at least a part of the solution would  build successfully.

The What

With BDD, you tackle it from the requirements side, by writing tests on what the module should and should not do.  One of the great qualities of BDD also is that it stresses on readability.  This means that instead of test method names like TestOvertime(), you get method names like WhenMoreThan40Hours_UserShouldGetPaidOvertime().  That’s the first test.  Second could be something like WhenCalculatingOvertime_UserMustBeEligibleForOvertime().

Now, combine this with the strength of a mocking framework, and you might never go back to top-down.  If you have not heard of this before, mocking frameworks do not highlight your bad code and laugh at you.  In this context, it helps you mock or impersonate your dependencies.

In laymen’s terms, if you are testing a method that calculates hours for an employee and the information is supposed to come from a database, in this case, the database is a dependency.  If you attempt to connect to a database to get your test data and the connection or the query fails for some reason, your test may fail for the wrong reason.  A mock framework let’s you “mock” a call to the database and return test data.

I will get into code details and working with RhinoMocks in a post shortly following this one, but here is what some sample code may look like.

The How

//mock framework - RhinoMocks
var mock = new MockRepository();

//this creates a mock interface of the IDatabase interface
//you don't even need to implement the code
//for this interface in a class to use it here.
var myDatabase = mock.StrictMock();

var employee = new Employee(myDatabase); //constructor takes in IDatabase object

using(mock.Record())
{
      Expect.Call(myDatabase.
                GetHoursWorkedForEmployeeID("123"))
                .Return(40);
}
using(mock.Playback())
{
     Assert.AreEqual(employee.IsOvertimeEligible,false);
}

 

The Why

By writing your unit tests first against your requirements, you end up building a system that is unit-test-ready and is very tight with the requirements.

In addition, this is also beneficial when working in a multi-developer team, by having your unit tests written, this ensures that when a developer goes into your module and changes functionality that may work for his module, but breaks yours, it won’t let him check in the solution, but this requires a bit of extra configuration work with MSBuild and Microsoft Team Foundation Server.

Every time you build a new method or make a change to the system, you can run your tests to make sure the new/modified functionality did not break any existing functionality.

In a nutshell, while this may seem like BDD would take longer, you should consider that time you spend on the dreaded back-and-forth with your QA team as they uncover your bugs and easter eggs can be virtually eliminated if you deliver a solution that unit tested itself at compile time or when you checked it into your source control repository.

Share

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>