Behavior Driven Development -- An Evolution in Testing
|
Behavior Driven Development (BDD) is not a revolution in testing, but an
evolution in how we software engineers think about program design. BDD was born
from the observation that most developers were not realizing the full potential
of Test Driven Development (TDD). Many people say that TDD, done well, is not
about testing but about design. However, the language of "testing"
impedes novices and journeymen from realizing its full potential. In this
article I will introduce Behavior Driven Development, explore the rationale
behind it, and introduce Open Source tools, some new, some familiar, that you
can use to get you started.
Extreme Programming (XP) popularized the concept of Test Driven Development as
a response to the reality that developers never seemed to have time to write
unit tests. In response, taking developer testing to the extreme (pun
intended), XP tells us to write tests before the code. The pattern: write a
failing test, then write the code to satisfy that test. Lather, rinse, repeat.
As TDD practitioners have refined their art, the TDD community came to the
realization that the real benefit of TDD it not about testing -- it is about
design. Done well, TDD is a tool for creating supple, testable, and decoupled
designs between collections of collaborating classes.
However, the majority of developers practicing TDD have not made this mental
leap. People started wondering why.
The Evolution of the TDD Developer
The path to enlightenment for a developer starting to practice TDD typically
follows a pattern.[i]
She,
- Starts using a unit testing
framework; such as JUnit or NUnit.
- Starts gaining confidence in
the code due to the existence of tests. Code can be refactored with impunity.
- Starts writing tests before
the code.
- Realizes that tests, both
hers an others, can be used as documentation for code.
- Begins writing tests first to
design the class API.
- Realizes that by writing
tests first, she is specifying the behavior of the class.
- Discovers the full potential
of TDD by specifying interactions between collaborating objects by using
mocks.
These last steps are truly the "Ah-ha!" moment of TDD.
Viewing the unimplemented class through the lens of a test, you realize that
specifying the externally visible behavior of your class before the
implementation allows you to define the behavior of your class.
Definition of class behavior and interaction with collaborating classes
is design. During this process questions can be asked about this class
and its collaborators -- is this class doing too much? Too little? What are the
externally visible behaviors of this class?
What impedes the developer from reaching this elevated level of software
development? Two things. How we think about the problem of "testing"
and the tools we use.
Language is the Leverage
Sapir and Whorf were American linguists in the ‘30s who developed the
Sapir-Whorf Hypothesis. One of the principles of the hypotheses, the
linguistic determinism principle, asserts that language determines the way we
think, that is,
"There is a systematic
relationship between the grammatical
categories of the language a person speaks and how that person
both
understands the world and behaves in it."[ii]
Which is to say, the language we use affects how we think about the world.
This language of testing is given to us by the tools, namely xUnit. The classes
we create are called 'Test' cases. Collections of test cases are 'Test' suites.
Methods must begin with the word 'test'.[iii]
The very word 'testing' implies an activity that happens last. From the
dictionary 'testing' is defined as:
"To take measures
to check the quality, performance, or
reliability of (something), esp. before putting it into
widespread
use or practice."[iv]
Within the body of test cases we make 'assertions' about our code. Assertions
are defined as:
"A confident and
forceful statement of fact or belief."[v]
The language of testing shapes our thought process into trying to answer the
question "How does my class work, and is it right?" Instead we should
be answering the question "What does my class do?" As Bob Martin has
said, "Testing is about Specification, not Verification."[vi]
If I Only Had a Hammer!
Language this not the only place where our tools hinder our progress. Most integrated
development environments that support xUnit provide some 'helpful' features for
generating test cases, such as creating one test case per class and creating
one test method for each (and every) method in a class.
Now these features can be helpful in the beginning when a developer is still
new to the syntax of a test case. However, if (mis)taken as a best practice we
learn bad testing habits.
Testing internal state and private methods are another bad habit. The externally
visible behavior of a class has nothing to do with its internal state. Testing
private methods is a test smell; it usually indicates that a class is doing too
much and should be broken into several classes, each with their own behavior,
tested independently.
Test organization can be a source of another test smell. As the number of test
methods in test classes increases, it becomes too large to manage, maintain,
and understand. A class may have behavior in several different scenarios, each
scenario with many distinct behavior. Tests should be organized by functional
context not simply by class name.
The language and tools of unit testing limit the activity of TDD to one of
verification. BDD provides new tools, approaches, and language to approach software
development from a different angle. One such BDD tool is rSpec, written in
Ruby.
Behavior Driven Development Using rSpec
The rSpec project has been blazing the BDD trail by leveraging the
flexibility of Ruby to create a domain specific language around the
specification of behavior.[vii]
An example:
describe Stack do
before do
@stack = Stack.new
end
it "should be empty after construction" do
@stack.length.should == 1
end
end
Several things to note about this example;
1. rSpec requires that you describe what behavior is being expressed. You may
no longer leave empty the descriptions passed to assert().
2. We 'describe' a class, or a class in a given context. Specifically we
describe the behavior of a class.
3. We use the word 'should' to specify class behavior. The choice of 'should'
over 'shall' or 'must' is both subtle and important. It allows the reader of
the spec to ask, "Well, should it?"
4. We again use the word 'should' when declaring our expected outcome.
Compare the same example to a xUnit test (in this case Ruby's Test::Unit).
class StackTestCase < TestUnit::TestCase
def setup
@stack = Stack.new
end
def test_empty_stack do
assert_equal(0, @stack.length)
end
end
The mechanics of both examples are the same; setup the scenario and then
declare some expectation. However, nothing in the latter example leads you to
think about behavior. rSpec gives us the language to reason about the behavior
of the class.
rSpec, because of its Ruby underpinnings, has an advantage for language and
syntactic flexibility. However, if you're not using Ruby, all is not lost.
There are several BDD frameworks for both Java and .NET.[viii]
However, the easiest one to use is the testing framework you probably are
already using, xUnit. Try this experiment on your next test case:
1. Name your test class based on one of the contexts, or scenarios, your class
will execute in. For example, defining the behavior of a Stack class:
class AnEmptyStack_TestCase inherits TestCase {...}
2. Put 'should' in your test method names along with a short description of
intended behavior:
public void test_shouldHaveOneElementWhenAddedTo() {}
By changing the language you use to think about the classes you write you'll
start to change how you think about the behavior of your classes. Describing
only the externally observed behavior will help you stop testing internal
state, stop testing private methods, and stop trying to validate your code.
No fancy tools needed.
About the author
Bob
Cotton is a Test Architect at Rally Software
Development. With over 12 years of developing and testing web applications,
Bob has extensive experience testing applications using open source testing tools.
From unit, load, functional, and GUI testing Bob has applied many tools and
techniques to testing large scale, SaaS (Software as a Service) applications.
Most recently at Rally, Bob has helped shape an Agile software methodology by
including unique approaches to software test automation. Automated testing,
using tools such as FitNesse and Selenium, is integrated into the development
process from the beginning of each iteration. Prior to joining Rally, Bob worked
as the System Architect at SynXis Corp, where he helped build and test a
high-volume hotel reservation system. Bob holds a B.S. in computer science from
George Mason University.
[i] See http://behaviour-driven.org/Introduction
[ii] See Wikipedia http://en.wikipedia.org/wiki/Sapir%E2%80%93Whorf_hypothesis
2007-08-24
[iii] This is changing with newer test tools, such as
JUnit 4 and TestNG.
[iv] Dictionary.app Version 1.0.1. Apple OS X
[v] Dictionary.app. Version 1.0.1. Apple OS X
[vi] From a presentation given to the Chicago Process Improvement Network,
3/5/2003. http://geocities.com/chigaco_spin/meet0303.html
[vii] http://rspec.rubyforge.org/
[viii] http://behavior-driven.org/implementations
|