In pursuit of code quality
Adventures in behavior-driven development
Isn't it time you learned how to JBehave?
Content series:
This content is part # of # in the series: In pursuit of code quality
This content is part of the series:In pursuit of code quality
Stay tuned for additional content in this series.
Developer testing is clearly a good thing. Testing done early — say, as you write your code — is an even better thing, especially when it comes to code quality. Write your tests first and you win the blue ribbon. The added momentum of being able to examine the behavior of your code and debug it preemptively is undeniably high-speed.
Even knowing this, we're nowhere near the critical mass that would make writing tests before writing code a standard practice. Just as TDD was an evolutionary next step extending from Extreme Programming, which pushed unit-testing frameworks into the limelight, evolutionary leaps are waiting to be made from the foundation that is TDD. This month, I invite you to join me as I make the evolutionary leap from TDD to its slightly more intuitive cousin: behavior-driven development (BDD).
Behavior-driven development
While test-first programming works for some people, it doesn't work for everyone. For every application developer who avidly embraces TDD, there are many others who actively resist it. Even with the abundance of testing frameworks like TestNG, Selenium, and FEST, the reasons for not testing code are manifold.
Two common reasons for not doing TDD are "There's not enough time for testing" and "The code is too complex and hard to test." Another hurdle in test-first programming is the test-first concept itself. Many of us view testing as a tactile activity, more concrete than abstract. Experience tells us that it isn't possible to test something that doesn't already exist. For some developers, given this conceptual framework, the idea of testing first is an oxymoron.
But what if, rather than thinking in terms of writing tests and how to test things, you thought about behavior, instead? By behavior, I mean how an application should behave — in essence, its specification.
As it turns out, you already think this way. We all do. Watch.
Frank: What's a stack?
Linda: It's a data structure that collects objects in a first in, last out (or last in, first out) manner. It usually has an API with methods likepush()
andpop()
. Sometimes you'll see apeek()
method, as well.
Frank: What doespush()
do?
Linda:push()
takes an input object, sayfoo
, and places it into an internal container (like an array).push()
usually doesn't return anything either.
Frank: What if Ipush()
two things, likefoo
and thenbar
?
Linda: The second object,bar
, should be on top of the conceptual stack (containing at least two objects), so that if you callpop()
,bar
should come off instead of the first object, which, in your case, isfoo
. If you calledpop()
again, thenfoo
should be returned and the stack should be empty (assuming there wasn't anything in it before you added the two objects).
Frank: Sopop
removes the most recent item placed into the stack?
Linda: Yes,pop()
should remove the top item (assuming there are items to remove).peek()
follows the same rule, but the object isn't removed.peek()
should leave the top item on the stack.
Frank: What if I callpop()
without having pushed anything?
Linda:pop()
should throw an exception indicating that nothing has been pushed yet.
Frank: What if Ipush()
null
?
Linda: The stack should throw an exception becausenull
isn't a valid value topush()
.
Notice anything particular about this conversation (aside from the fact that Frank didn't major in computer science)? Nowhere was the word "test" used. The word "should" slipped in here and there quite naturally, however.
Doing what comes naturally
BDD isn't anything new or revolutionary. It's just an evolutionary offshoot of TDD in which the word "test" is replaced by the word "should." Semantics aside, many people find the concept of should a much more natural development driver than the concept of testing. Thinking in terms of behavior (shoulds) somehow paves the way into writing specification classes first, which, in turn, can be a very efficient implementation driver.
Using the conversation between Frank and Linda as a basis, let's see how BDD drives development in just the way that TDD was intended to promote.
JBehave
JBehave is a BDD framework for the Java™ platform inspired by the xUnit paradigm. As you can probably guess, JBehave stresses the word should, rather than test. Just like JUnit, you can run JBehave classes in your favorite IDE and via your preferred build platform, such as Ant.
JBehave lets me create a behavior class much like I would in JUnit;
however, in the case of JBehave, I don't need to extend from any particular
base class, and all my behavior methods need to start with should
, rather than test
), as shown in Listing 1.
Listing 1. A simple behavior class for a stack
public class StackBehavior { public void shouldThrowExceptionUponNullPush() throws Exception{} public void shouldThrowExceptionUponPopWithoutPush() throws Exception{} public void shouldPopPushedValue() throws Exception{} public void shouldPopSecondPushedValueFirst() throws Exception{} public void shouldLeaveValueOnStackAfterPeep() throws Exception{} }
The methods defined in Listing 1 all start with should and they all create a
human-readable sentence. The resulting StackBehavior
class
describes many of the features of the stack in the conversation between Frank and Linda.
For instance, Linda stated that a stack should throw an exception if a user
attempted to place null
onto it. Check out the first behavior
method in the StackBehavior
class: It's called shouldThrowExceptionUponNullPush()
. The other methods follow the
same pattern. This descriptive naming pattern (which is by no means unique to JBehave
or BDD) makes it possible to state a failing behavior in a human readable manner, as
you'll see shortly.
Speaking of shouldThrowExceptionUponNullPush()
, how would
you verify this behavior? It seems to me that you'd first need a push()
method on a Stack
class, which is
easy to define.
Listing 2. A simple stack definition to facilitate exploring behavior
public class Stack<E> { public void push(E value) {} }
As you can see, I've coded the minimum amount of a stack so I can
start fleshing out the required behavior first. As Linda stated, the behavior is
simple: If someone calls push()
with a null
value, the stack should throw an exception. Now look at
how I've defined this behavior in Listing 3.
Listing 3. The stack should throw an exception if null is pushed
public void shouldThrowExceptionUponNullPush() throws Exception{ final Stack<String> stStack = new Stack<String>(); Ensure.throwsException(RuntimeException.class, new Block(){ public void run() throws Exception { stStack.push(null); } }); }
Great expectations and overrides
A few things are happening in Listing 3 that are unique to JBehave, so let
me explain. First, I create an instance of the Stack
class and limit it to String
types (via Java 5 generics). Next, I use JBehave's
expectation framework to essentially model my desired behavior. The
Ensure
class is analogous to JUnit or TestNG's
Assert
type; however, it adds a series of methods
that facilitate a more readable API (this is often called literate
programming). In Listing 3, I've ensured that a RuntimeException
will be thrown if push()
with null
is called.
JBehave also introduces a Block
type, which is
implemented by overriding the run()
method with your
desired behavior. Internally, JBehave ensures that your desired exception type
isn't thrown (and, therefore, caught), a failure state is generated.
If I run the behavior from Listing 3 now, I should see a failure. As currently coded,
the push()
method doesn't do anything; so there is no way an
exception will be generated, as you can see from the output in Listing 4.
Listing 4. The behavior I want isn't there
1) StackBehavior should throw exception upon null push: VerificationException: Expected: object not null but got: null:
The sentence in Listing 4, "StackBehavior should throw exception
upon null push
," mimics the behavior's name (shouldThrowExceptionUponNullPush()
) along with the name of the class.
Essentially, JBehave is reporting that it didn't get anything when it ran the desired
behavior. Of course, my next step is to make that behavior pass, which I've done by
checking for null
in Listing 5.
Listing 5. Adding the specified behavior in the stack class
public void push(E value) { if(value == null){ throw new RuntimeException("Can't push null"); } }
When I rerun my behavior, everything is good to go, as shown in Listing 6.
Listing 6. Success!
Time: 0.021s Total: 1. Success!
Behavior drives development
Doesn't the output in Listing 6 look similar to JUnit's output? That's probably not a
coincidence, is it? As mentioned, JBehave is modeled after the xUnit paradigm
and even supports fixtures via setUp()
and tearDown()
. Given that I'm probably going to be using a Stack
instance throughout my behavior class, I might as well push (no
pun intended) that logic into a fixture, as I've done in Listing 7. Note that
JBehave will follow the same fixture contract that JUnit follows — that is, it will run
a setUp()
and tearDown()
for
every behavior method.
Listing 7. Fixtures in JBehave
public class StackBehavior { private Stack<String> stStack; public void setUp() { this.stStack = new Stack<String>(); } //... }
Moving on to the next behavior method, shouldThrowExceptionUponPopWithoutPush()
indicates I'll have to
ensure similar behavior to that of shouldThrowExceptionUponNullPush()
from Listing
3. As you can see in Listing 8, there isn't any particular magic going on
— or is there?
Listing 8. Ensuring the behavior of pop
public void shouldThrowExceptionUponPopWithoutPush() throws Exception{ Ensure.throwsException(RuntimeException.class, new Block() { public void run() throws Exception { stStack.pop(); } }); }
As you've probably figured out, Listing 8 won't actually compile at this point
because pop()
hasn't been written yet. But before I start to do
that (write pop()
), let's think a few things through.
Ensuring behavior
Technically, I could just implement pop()
to only throw an
exception at this point, regardless of calling order. But going down this behavior
route encourages me to think about an implementation that supports my desired
specification. In this case, ensuring that pop()
throws an
exception if push()
hasn't been called (or logically, if the
stack is empty) means that the stack has a state. And as Linda mused earlier, a stack
usually has an "internal container" that actually holds items. Accordingly, I can create
an ArrayList
for the Stack
class
that holds values passed into the push()
method, as shown
in Listing 9.
Listing 9. A stack needs an internal way to hold objects
public class Stack<E> { private ArrayList<E> list; public Stack() { this.list = new ArrayList<E>(); } //... }
Now I can code the behavior for the pop()
method, which
ensures that if the stack is logically empty, an exception will be thrown.
Listing 10. Implementing pop just got easier
public E pop() { if(this.list.size() > 0){ return null; }else{ throw new RuntimeException("nothing to pop"); } }
When I run the behavior in Listing 8, things work as expected: Because the stack isn't holding any values (hence, its size isn't greater than zero), an exception is thrown.
The next behavior method is called shouldPopPushedValue()
,
which turns out to be easy to specify. I simply push()
a
value ("test"
) and ensure that when I call pop()
, that same value is returned.
Listing 11. If you push a value, it should pop off, right?
public void shouldPopPushedValue() throws Exception{ stStack.push("test"); Ensure.that(stStack.pop(), m.is("test")); }
Dial 'M' for Matcher
In Listing 11, I ensure that pop()
returns the value "test"
. In the course of using JBehave's Ensure
class, you'll often find that you need a richer way to specify
expectations. JBehave meets this need by offering a Matcher
type for implementing rich expectations. In my case, I chose to reuse JBehave's UsingMatchers
type (the m
variable in
Listing 11) so I could use methods like is()
, and()
, or()
, and a host of other neat
mechanisms for building a more literate style of expectations.
The m
variable from Listing 11 is a static member of the
StackBehavior
class, as shown in Listing 12.
Listing 12. UsingMatchers in the behavior class
private static final UsingMatchers m = new UsingMatchers(){};
With the new behavior method coded in Listing 11, it's time to run it — but doing so indicates a failure, as shown in Listing 13.
Listing 13. My newly coded behavior doesn't work
Failures: 1. 1) StackBehavior should pop pushed value: java.lang.RuntimeException: nothing to pop
What happened? It turns out that my push()
method wasn't
finished. Back in Listing 5, I coded the bare minimum
implementation to get my behavior to work. Now it's time to finish the job, by actually
adding the pushed value into the internal container (if the value isn't null
). I do this in Listing 14.
Listing 14. Wrap up that push method
public void push(E value) { if(value == null){ throw new RuntimeException("Can't push null"); }else{ this.list.add(value); } }
But wait — when I rerun the behavior, it still fails!
Listing 15. JBehave reports a null value instead of an exception
1) StackBehavior should pop pushed value: VerificationException: Expected: same instance as <test> but got: null:
At least the failure in Listing 15 is different from Listing 13. In this case, rather
than an exception being thrown, the "test"
value isn't being
found; null
is being popped. Looking closely at Listing 10 reveals the issue: I initially coded the pop()
method to return null
if the
internal container had anything in it. Well, that's easy to fix.
Listing 16. Time to finish coding this pop method
public E pop() { if(this.list.size() > 0){ return this.list.remove(this.list.size()); }else{ throw new RuntimeException("nothing to pop"); } }
But now if I rerun the behavior, I get a new failure.
Listing 17. Yet another failure
1) StackBehavior should pop pushed value: java.lang.IndexOutOfBoundsException: Index: 1, Size: 1
A close reading of the information in Listing 17 uncovers the issue: I need to account
for 0 when dealing with an ArrayList
.
Listing 18. Accounting for 0 fixes the issue
public E pop() { if(this.list.size() > 0){ return this.list.remove(this.list.size()-1); }else{ throw new RuntimeException("Nothing to pop"); } }
The logic of stacks
Thus far, I've managed to implement push()
and pop()
in such a manner as to permit a number of behavior methods to
pass. I've yet to tackle the meat of the stack, however, which is the logic associated
with multiple push()
es and pop()
s,
along with throwing in an occasional peek()
.
First, I'll ensure that the basic algorithm of my stack (first in, last out) is sound, via
the shouldPopSecondPushedValueFirst()
behavior.
Listing 19. Ensuring typical stack logic
public void shouldPopSecondPushedValueFirst() throws Exception{ stStack.push("test 1"); stStack.push("test 2"); Ensure.that(stStack.pop(), m.is("test 2")); }
The code in Listing 19 works as planned, so I'll implement another behavior method (in
Listing 20) to ensure that using pop()
twice shows proper
behavior, as well.
Listing 20. Going deeper with stack behavior
public void shouldPopValuesInReverseOrder() throws Exception{ stStack.push("test 1"); stStack.push("test 2"); Ensure.that(stStack.pop(), m.is("test 2")); Ensure.that(stStack.pop(), m.is("test 1")); }
Moving on, I'd like to ensure that peek()
works as intended.
As Linda said, peek()
follows the same rules as pop()
, but "should leave the top item on the stack." Accordingly,
I've implemented the behavior for the shouldLeaveValueOnStackAfterPeep()
method in Listing 21.
Listing 21. Ensuring that peek leaves the top item on the stack
public void shouldLeaveValueOnStackAfterPeep() throws Exception{ stStack.push("test 1"); stStack.push("test 2"); Ensure.that(stStack.peek(), m.is("test 2")); Ensure.that(stStack.pop(), m.is("test 2")); }
Because peek()
hasn't been defined yet, the code in Listing
21 won't compile. In Listing 22, I've defined a bare-bones implementation of peek()
.
Listing 22. Peek is required, of course
public E peek() { return null; }
Now the StackBehavior
class will compile, but it still won't
run.
Listing 23. No surprise that null was returned, right?
1) StackBehavior should leave value on stack after peep: VerificationException: Expected: same instance as <test 2> but got: null:
Logically, peek()
doesn't remove the item from the
internal collection, it basically just passes a pointer to it. Consequently, I use the
get()
method on ArrayList
, rather
than remove()
, as shown in Listing 24.
Listing 24. Don't remove it
public E peek() { return this.list.get(this.list.size()-1); }
Nothing to see here
Now rerunning the behavior from Listing 21 yields a passing
grade. Doing this exercise has revealed an issue, however: What is the behavior of peek()
if nothing is there? If a pop()
with nothing in it should throw an exception, should peek()
do the same?
Linda didn't say anything about this, so apparently, I need to flesh out some new
behavior myself. In Listing 25, I've coded the scenario for "What happens if peek()
is called without a push()
."
Listing 25. What happens if peek is called without a push?
public void shouldReturnNullOnPeekWithoutPush() throws Exception{ Ensure.that(stStack.peek(), m.is(null)); }
Once again, no surprises here. Things blew up, as you can see in Listing 26.
Listing 26. It's nothing to peek at
1) StackBehavior should return null on peek without push: java.lang.ArrayIndexOutOfBoundsException: -1
The logic to fix the defect is quite similar to the logic in pop()
, as you can see in Listing 27.
Listing 27. This peek() needs some fixing
public E peek() { if(this.list.size() > 0){ return this.list.get(this.list.size()-1); }else{ return null; } }
All my modifications and fixes to the Stack
class result in
the code you see in Listing 28.
Listing 28. Ah, a working stack
import java.util.ArrayList; public class Stack<E> { private ArrayList<E> list; public Stack() { this.list = new ArrayList<E>(); } public void push(E value) { if(value == null){ throw new RuntimeException("Can't push null"); }else{ this.list.add(value); } } public E pop() { if(this.list.size() > 0){ return this.list.remove(this.list.size()-1); }else{ throw new RuntimeException("Nothing to pop"); } } public E peek() { if(this.list.size() > 0){ return this.list.get(this.list.size()-1); }else{ return null; } } }
At this point, the StackBehavior
class runs seven
behaviors that ensure the Stack
class works according to
Linda's (and a bit of my own) specification. The Stack
class
could probably use some refactoring (maybe the pop()
method
should call peek()
as a test, rather than the size()
check?), but thanks to my behavior-driven process so far, I
have the infrastructure to make the changes in near total confidence. If I break
something, I'll be quickly notified.
In conclusion
What you may have noticed about this month's exploration in behavior-driven development, or BDD, is that Linda was, in essence, the customer. You might think of Frank as the developer in this scenario. Take away the domain (in this case, data structures) and replace it with something else (say, a call center application) and the exercise is similar. Linda, the customer or domain expert, says what the system, feature, or application should do and someone like Frank uses BDD to ensure he has heard her correctly and implement her requirements.
For many developers, the shift from test-driven development to BDD is a smart move. With BDD, you don't have to think about tests, you can just pay attention to the requirements of your application and ensure that the application behavior does what it should to meet those requirements.
In this case, using BDD and JBehave made it easy for me to implement a working stack based on Linda's specifications. I just listened to what she was saying and then built the stack accordingly, by thinking in terms of behavior first. In the process, I also managed to discover a few things Linda had forgotten about stacks.
Downloadable resources
Related topics
- "Behavior-driven testing with RSpec" (Bruce Tate, developerWorks, August 2007): One of the most promising innovations in developer testing in the past year is the introduction and rapid growth of RSpec, a behavior-driven testing tool. Learn how RSpec can change the way you think about testing.
- "Introducing BDD" (Dan North, DanNorth.net, September 2006): Read how Dan North came up with BDD as a practice.
- "Using BDD to drive development" (Andrew Glover, testearly.com, July 2007): Andrew offers another chance to see how BDD drives development, also based on using JBehave.
- Download JBehave: A BDD framework for the Java platform that started it all.
- In pursuit of code quality series (Andrew Glover, developerWorks): Learn more about writing quality-focused code.