In the last installment of our series, I presented a simple example of a traditional unit test. In this article, we’ll discuss a completely different way to use unit tests: Test First Programming.
In the book Extreme Programming Explained: Embrace Change published in October 1999, Kent Beck introduced what was at the time a rather foreign concept to software development: Writing unit tests to drive software design. Described as Test-First Programming, the technique involves first writing a test to verify a small increment of desired functionality and then writing code to make the test pass. This process is then repeated until no additional functionality is desired.
Another practice introduced alongside Test-First Programming was Incremental Design. Incremental design is the process of evolving the design of an application as new functionality is developed and better design approaches are recognized. Due to the fact that software development is an inherently empirical process, following a defined model where the software is designed up front generally leads to waste and rework. Incremental design postpones design decisions until needed, introducing patterns and frameworks as a byproduct of refactoring as well as eliminating any flexibility that is unused.
Test Driven Development
Later, these ideas were refined into a practice known as Test Driven Development. Test-Driven Development can be described simply as the ideas of Test-First Programming coupled with Incremental Design. In a later book entitled Test-Driven Development By Example published in November of 2002, Beck presents a refined process involving the writing of a failing test, taking the simplest steps possible to make the test pass, and removing any duplication introduced. This process was described succinctly as Red/Green/Refactor, where red and green referred to the colors typically presented by test runners to indicate failing and passing tests respectively.
While the practice of Test-Driven Development utilized unit tests primarily for driving application design, its outgrowth from traditional unit testing practices were still evident in its style, language, and supporting frameworks. This presented an obstacle for some in understanding and communicating the concepts behind the practice.
In the course of teaching and practicing TDD, Dan North began encountering various strategies for designing tests in a more intent-revealing way which aided in his understanding and ability to convey the practice to others. As a way to help distinguish the differences between what he saw as an exercise in defining behavior rather than testing, he began describing the practice of Test-Driven Development as Behavior Driven Development. By shifting the language to the discussion about behavior, he found that many of the questions he frequently encountered in presenting TDD concepts became easier to answer. North went on to introduce a framework incorporating this shift in thinking called JBehave. North’s work on JBehave along with his promotion of the ideas behind Behavior-Driven Development served as the inspiration for the creation of many other frameworks including RSpec, Cucumber, NBehave, NSpec, SpecFlow, MSpec and many others.
Behavior-Driven Development Approaches
Two main approaches have emerged from BDD practices which I’ll categorize as the Plain-Text Story approach and the Executable Specification approach.
With the Plain-Text Story approach, software features are written as a series of plain-text scenarios which the framework associates with executable steps used to drive feature development. This approach is typically associated with a style referred to Given/When/Then (GWT), named after the grammar set forth by Dan North. This style is facilitated by popular BDD frameworks such as Cucumber and JBehave.
The following is an example story using Gherkin grammar, the language used by Cucumber and other Plain-Text Story BDD frameworks:
In order to calculate totals
As a user
I want to calculate the sum of multiple numbers
Scenario: Add two numbers
Given I have entered 10 into the calculator
And I have entered 50 into the calculator
When I press add
Then the result should be 60 on the screen
Using Cucumber, the “Given” step in this scenario may then be associated to the following step definition in Ruby:
Using SpecFlow, the same “Given” step can also be associated to the following step definition in C#:
In contrast to the use of plain-text stories, the Executable Specification approach uses source code as the medium for expressing specifications in the language of the business. Executable Specifications can be written using existing unit-testing frameworks such as JUnit, NUnit, and MSTest, or are written in frameworks designed exclusively to facilitate the Behavior-Driven Development style such as RSpec and MSpec.
One style, referred to as Context/Specification, expresses software features in terms of specifications within a given usage context. In this style, each unique way the software will be used is expressed as a context along with one or more expected outcomes.
While specialized frameworks have been created to support the Context/Specification style, this approach can also be facilitated using existing xUnit frameworks. This is generally accomplished by creating a Context base class which uses the Template Method pattern to facilitate the desired language.
The following example demonstrates this technique using the NUnit framework:
In addition to providing a Contact base class, the NUnit framework’s TestAttribute can also be sub-classed to aid in moving toward specification-oriented language:
By extending the Context base class, NUnit can then be used to write Context/Specification style BDD specifications:
Working, Maintainable Software That Matters
The ideas behind Test-First Programming continue to be refined by the software development community and have lead to still other variations in name and style, including Example Driven-Development, Specification-Driven Development, Need-Driven Development, and Story Test-Driven Development. Regardless of its label or flavor, the refinements of Test-First Programming can be distilled down to the following goal: The creation of working, maintainable software that matters. That is to say, the software should be functional, should be easily maintainable and should be relevant to needs of our customers.
This time we discussed Test-First Programming and traced its steps to today’s practice of Behavior-Driven Development. Next time we’ll discuss Test-Driven Development in more detail and walk through a Test First example using the TDD methodology while incorporating some of the Behavior-Driven Development concepts presented here.