In pursuit of code quality: Adventures in behavior-driven development

Isn't it time you learned how to JBehave?

Test-driven development (TDD) is a great idea in practice, but some developers just can't get over the conceptual leap associated with that word test. In this article, Andrew Glover shows you a more natural way to integrate the momentum of TDD into your programming practice. Get started with behavior-driven development (BDD) (via JBehave) and see for yourself what happens when you focus on program behaviors, rather than outcomes.

Andrew Glover, President, Stelligent Incorporated

Andrew GloverAndrew Glover is president of Stelligent Incorporated, which helps companies address software quality with effective developer testing strategies and continuous integration techniques that enable teams to monitor code quality early and often. Check out Andy's blog for a list of his publications.



18 September 2007

Also available in Chinese Russian Japanese

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).

Improve your code quality

Don't miss Andrew Glover's Code quality discussion forum for assistance with code metrics, test frameworks, and writing quality-focused code.

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 like push() and pop(). Sometimes you'll see a peek() method, as well.

Frank: What does push() do?

Linda: push() takes an input object, say foo, and places it into an internal container (like an array). push() usually doesn't return anything either.

Frank: What if I push() two things, like foo and then bar?

Linda: The second object, bar, should be on top of the conceptual stack (containing at least two objects), so that if you call pop(), bar should come off instead of the first object, which, in your case, is foo. If you called pop() again, then foo should be returned and the stack should be empty (assuming there wasn't anything in it before you added the two objects).

Frank: So pop 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 call pop() without having pushed anything?

Linda: pop() should throw an exception indicating that nothing has been pushed yet.

Frank: What if I push()null?

Linda: The stack should throw an exception because null isn't a valid value to push().

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

Which framework should I use?

Annotations make it possible to practice BDD using JUnit and TestNG. I find it more interesting to use a BDD framework like JBehave, which provides features for defining behavior classes, such as an expectation framework that facilitates a more literate style of programming.

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. You may recall a similar pattern of overriding a convenience class in my previous article about unit testing Ajax with the Google Web Toolkit; in that case, the override was done with GWT's Timer class.

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

About the UsingMatchers type

You might note that the code in Listing 12 isn't exactly elegant. The m in Listing 11 does affect the readability slightly ("ensure that pop's value m (what the?) is test"). You can avoid using the UsingMatchers type by extending a special base class (UsingMiniMock) provided by JBehave. This way, the last line in Listing 11 becomes Ensure.that(stStack.pop(), is("test")), which is a bit more readable.

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.

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Select information in your profile (name, country/region, and company) is displayed to the public and will accompany any content you post. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Web development
ArticleID=254866
ArticleTitle=In pursuit of code quality: Adventures in behavior-driven development
publish-date=09182007