by Rod Coffin

Behavior-driven development with easyb

feature
Sep 4, 200813 mins

Groovy DSL lets developers and domain experts speak the same language

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:

  1. Write a test. This test may refer to programming elements that don’t exist yet.
  2. Make it compile. Write just enough code to make the test compile.
  3. 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.
  4. Make it pass. Write just enough code to make the test pass.
  5. 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.

Introducing easyb

easyb is a BDD framework for the Java platform that makes it (you guessed it) easy to describe and verify system behaviors at all levels. With easyb, you can write user stories, develop specifications for system components, describe UI interactions, and much more. But easyb isn’t the only BDD framework; you have many other options, including JBehave, JDave, and RSpec. So what is unique about easyb? Because of its implementation as a Groovy-based domain-specific language (DSL), easyb features a uniquely concise and highly expressive language for expressing behaviors.

easyb specifications are represented as Groovy scripts. Because of the nature of the Groovy language, and the leniency afforded to Groovy scripts over classes, easyb specifications are relatively free of programmer-specific syntax, placing the focus instead on communicating behaviors. For example, Listing 1 is a completely valid minimal expression of behavior for a frequent flyer rewards calculator represented as an easyb specification.

Listing 1. Behavior for a frequent flyer rewards calculator

it 'should calculate rewards points for a segment flown'

This type of specification, in which behavior is described but not actually bound to the system under test, is called a pending specification in easyb. Pending specifications are reported as pending by the easyb runner and are included in the story reports that easyb renders. This allows a team to create specifications at the beginning of a release or iteration and bind them to the system over time as features are implemented.

This description of behavior can be augmented by attaching code blocks that verify the behavior of the system under test, as shown in Listing 2.

Listing 2. Verifying behavior

it 'should calculate rewards points for a segment flown', {
     calculator = new RewardsCalculator()
     calculator.rewardsForSegmentWithDistance(750).shouldBe 750
 }

Listing 2 consists of three components: the it keyword, which identifies the block as a specification to be run by easyb; a string description of the specification, surrounded with quotes; and a closure block, in which Groovy code is written to bind the expressed behavior to the system under test. Notice also that the expanded version asserts the frequent flyer points that should be rewarded for flying a single segment using the shouldBe keyword. easyb’s DSL provides a rich set of expressions that allow expectations to be conveyed in a very readable manner. The easyb DSL is documented in more detail online and includes constructs for testing equality, negation, containment, nullness, types, and exceptions.

The specification in Listing 2 can now be run; but because the spec was written before the code necessary to make it pass, the spec will fail, as the RewardsCalculator class does not exist. After writing the RewardCalculator class shown in Listing 3, re-running the spec will result in success.

Listing 3. Making the spec pass by writing code

public class RewardsCalculator {
    public int rewardsForSegmentWithDistance(int total) {
        if (total < 0)
            throw new IllegalArgumentException("Cannot fly negative miles");
        return total;
    }
}

Once the spec passes, you can begin to modify it as you add functionality to your application. For example, you can assert that a collection contains specific elements, as in Listing 4.

Listing 4. Requiring specific elements in a collection

it 'should return gold, platinum, and executive as types', {
     ensure (new EliteStatusRegistry().listTypes()) {
         contains('gold')
         and
         contains('platinum')
         and
         contains('executive')
     }
 }

Listing 5 demonstrates how to establish the ways in which exceptions are thrown.

Listing 5. An exception for the behavior

it 'should calculate the sales tax for a purchase', {
     calculator = new RewardsCalculator()
     ensureThrows(IllegalArgumentException.class) {
         calculator.rewardsForSegmentWithDistance(-1)
     }
 }

These examples illustrate how easyb can be used to test system behaviors at the unit or component level. In easyb parlance, these are simply referred to as specifications and use an it should syntax when describing behaviors. easyb can also be used to verify systemwide behaviors through the use of user stories.

User stories

easyb is comparable to other executable documentation tools, such as Fit, Fitnesse, and Green Pepper, in that each provides the capability to capture system behaviors in order to communicate intent and bind these expectations to the system under test. But easyb stands out among these tools because its syntax really stays out of the way during the specification-writing process. Its concise, clutter-free specification-writing domain language frees the specification writer from distracting syntax, leaving the focus instead on the conversation and the system behaviors.

Consider again the frequent flyer rewards system you peeked at earlier. Throughout the development process, team members will sit down with domain experts to explore and capture system requirements. easyb facilitates the capture of these behaviors in the form of user stories. easyb provides support for user stories at two levels. First, a narrative description of the story can be optionally expressed in the popular as_a, i_want, so_that form. easyb also allows zero or more scenarios to augment this narrative and various conditions to be included as well.

Listing 6 illustrates a simple story narrative written in easyb expressing how airline miles are accrued when a passenger flies a single segment.

Listing 6. A story expressing how airline miles are accrued

narrative 'segment flown', {
     as_a 'frequent flyer'
     i_want 'to accrue rewards points for every segment I fly'
     so_that 'I can receive free flights for my dedication to the airline'
 }

The only elements of this document that are specific to easyb are the narrative, as_a, i_want, and so_that keywords, the quote marks, and the curly braces. This allows the conversation with the requirement donor to be more focused on system behaviors and less distracted by syntax, a benefit easyb offers over the other tools mentioned above. (The underscores in the keywords are an unfortunate consequence of how the easyb DSL is implemented as an extension to Groovy, and will be resolved in easyb 1.0.) A narrative of this type can be very easily understood by non-programmers and is very quick to write. This form of capturing a user story provides context around who benefits from an enhancement, and in what ways.

The narrative description of system behavior can be augmented with one or more scenarios that elaborate on the story description by providing concrete examples that are verifiable against the system. Listing 7 illustrates a scenario for the frequent flyer story narrative in Listing 6.

Listing 7. A frequent flier scenario

scenario segment flown', {
     given 'a frequent flyer with a rewards balance of 1500 points'
     when 'that flyer completes a segment worth 500 points'
     then 'that flyer has a new rewards balance of 2000 points'
 }

easyb also makes it easy to bind pending stories to the system under test. The scenario expressions can be augmented with closure blocks, allowing programmers to bind the specification to the system using whatever constructs the system’s design supports. Because easyb specifications are Groovy scripts, any Groovy programming constructs can be used in this binding process.

For example, the segment flown scenario described in Listing 7 can be bound to the system under test as shown in Listing 8.

Listing 8. Binding a scenario to the system under test

scenario 'segment flown', {
     given 'a frequent flyer with a rewards balance of 1500 points', {
         flyer = new FrequentFlyer(1500)
     }
     when 'that flyer completes a segment worth 500 points', {
         flyer.fly(new Segment(500))
     }
     then 'that flyer has a new rewards balance of 2000 points', {
         flyer.pointsBalance.shouldBe 2000
     }
 }

As a shared understanding grows between the developers and the product owner in the story-writing process, the act of binding these specifications to the system under test verifies this understanding and serves as a mechanism to determine if the system deviates from this expected behavior over time.

Running easyb

The previous sections illustrated how easy it is to capture behaviors of varying levels of granularity with easyb. But in order for these specifications to provide the most value, they will need to be executed as a part of the development process and build system.

During a development episode, easyb specifications can be run by setting up an external tool configuration in your IDE or, if you happen to use IntelliJ IDEA, by installing the IntelliJ easyb plugin. The process of getting an external tool up and running varies across IDEs, but generally involves setting up a run configuration that places the easyb JARs on the classpath and then invokes the easyb behavior runner’s main method, passing a list of space-separated specifications to run. The IntelliJ plugin simplifies this process by allowing specifications to be run in the same manner as JUnit tests, and provides graphical reporting of results in addition to the behavior runner’s console output. Figure 1 below shows the output of executing an easyb story in IntelliJ using the easyb plugin. This story was run just as a JUnit test would have been, and the results of the story are similar in form as well. (Click the image to enlarge it.)

Running an easyb story in IntelliJ using the easyb IntelliJ plugin is as simple as running a JUnit test.
Figure 1. Running an easyb story in IntelliJ using the easyb IntelliJ plugin is as simple as running a JUnit test

easyb also provides an Ant task and a Maven plugin with which you can run easyb specifications as a part of the build process. These both cause the build to fail if a non-pending specification or story fails, and can optionally generate story reports that contain story narrative and acceptance scenarios apart from the closures that bind them to the system.

Tooling is an important component of any development environment, and the tooling around easyb is already good and grows more and more so each day.

In conclusion

In this brief overview of easyb, you have explored the language’s approach to BDD and seen how it helps you think about your system as a collection of behaviors across various levels of granularity. By keeping the focus on behaviors instead of tests for specific classes and methods, BDD — and easyb — encourage you to write tests that are more descriptive of what the system should do and less tightly coupled to particular implementations.

Additionally, easyb’s approach for capturing user stories is less restrictive than the programmer-oriented syntaxes of comparable tools. easyb aims to stay out of the way of the story-writing process so that the center of attention is on the conversation taking place, not on the tool that is being used to capture that conversation. Furthermore, easyb does make it easy to bind these expressions of behavior to the system under test as a mechanism for validating the conversations.

As easyb draws closer to its 1.0 launch, now is a great time to give it a test drive and see for yourself how BDD and easyb can help you build better systems that more closely match your customers’ needs.

Rod Coffin is a principal consultant at Improving Enterprises, which provides training, consulting, and outsourcing services. He has over 10 years of professional software development experience across a wide variety of industries, technologies, and roles. He has coached and mentored several teams on agile software development and is equally passionate about the organizational and technical sides of effective software development. He is a frequent speaker at software conferences and has written many articles on a range of software development topics, including agility, enterprise software development, and semantics.