Behavior-driven development offers many of the benefits of test-driven development, but without the tight coupling to specific implementations. In this article Rod Coffin discusses the difference between the two development methods and explains the concepts of BDD. He then walks through an example in behavior-driven development with easyb -- a Groovy-based framework that employs a rich DSL understandable by both developers and domain experts.
Thanks to the success of extreme programming and its promotion of agile development practices, the software development community today is relatively familiar with test-driven development (TDD). In TDD, tests are written before production code, and compilation and testing failures provide the impetus to write production code to satisfy those tests. A typical TDD cycle follows these basic steps:
- Write a test. This test may refer to programming elements that don't exist yet.
- Make it compile. Write just enough code to make the test compile.
- Run the test and see that it fails. Verify that the test is failing for the right reason, which is that the functionality expressed in the test hasn't been implemented yet.
- Make it pass. Write just enough code to make the test pass.
- Refactor. Refactor both test and production code to keep both amenable to change.
Behavior-driven development (BDD) is similar in many ways to TDD, but is distinguished in several subtly powerful ways. The BDD cycle parallels the TDD cycle, except that the word test is replaced with the word spec. Although this change appears to be purely at the surface level, it actually represents a powerful shift in the thought process. BDD shifts the focus from implementation details to the behaviors that your system exhibits.
In TDD conventions, test classes and methods are named after the production classes and methods that they test. For example, if you are test-driving a frequent flyer application that has a class named
RewardsCalculator with a method named
calculate(), you would typically write a
RewardsCalculatorTest class with a
testCalculate() method. But this creates a situation in which your test code is tightly coupled to the structure of your production code. What happens if you move some of the functionality from the
calculate() method to another method or class? You would likely need to modify the structure of your test code to reflect the change.
BDD attempts to re-orient the focus of TDD away from the specific structure of implementation code and towards system behaviors. One advantage of this approach is that behaviors change less often than implementation details, and typically such changes are precipitated by intentional enhancements to the functionality of the system, not as a result of simple refactoring activities.
System behaviors can be also described with varying levels of granularity. For example, you can talk about behaviors that the system as a whole should exhibit, or behaviors that characterize individual components of the system. Systemwide behaviors are often captured as user stories or use cases. Finer-grained behaviors are usually less formally described and often are only captured in test cases. BDD can be a useful way to communicate and verify behaviors of both forms. In fact, BDD blurs the distinction between unit, integration, and system tests.
Specs developed in BDD can be written with the same tools used for TDD. For instance, you can use JUnit, and choose to name your JUnit tests with BDD syntax. Using the frequent flier example, you could name a JUnit test class
WhenFlyingASegment and create a method in it named
shouldRewardAirlineMiles(). This spec could now be run along with or in place of your JUnit tests in your IDE, build; and so on. Of course, there are specific tools that provide more specialized support for BDD, including the easyb framework.