Skip to main content

Behavior Driven Development - putting testing into perspective

Posted by johnsmart on February 19, 2008 at 12:26 AM PST

The ultimate aim of writing software is to produce a product that satisfies the end user and the project sponsor (sometimes they are the same, sometimes they are different). How can we make sure testing helps us obtain these goals in a cost-efficient manner?

To satisfy the end user (the person who ends up relying on your software to make his or her work easier), you need to provide the optimal feature set. The main challenge here is that the optimal feature set is not always what the users ask for, nor is it always what the BA comes up with at the start. So you need to keep on your toes, and be able to change direction quickly as the users discover what they really need. But that's the realm of Agile Development, and not really what I wanted to discuss here...

To satisfy the project sponsor (the person who has to fork out the cash), you need to satisfy your users, but you also need to write your application as efficiently as possible. Efficiency means writing code quickly, but it also means avoiding having to come back later on to fix silly mistakes. For example, I cn wriet ths tezt REASLDY QUIFKLY but if I don't keep an eye on the quality, the end user (you, the reader, in this case) will suffer. Your code needs to be reliable (not too many bugs) and maintainable (easy enought to understand so that the poor bastard who comes after you can work on the code with minimum hair loss).

So what has all this got to do with testing? Writing a test takes time and effort, so, ideally, you need to balance the cost of writing a test against the cost of _not_ writing_ the test. Does the test you are writing directly contribute to delivering a feature for the user? Will it lower long-term costs by making it more flexible and reliable? If your tests are to contribute positively to the global outcome of your project, you need to think about this, and design your tests so that they will provide the most benefit for the project as a whole.

It is fairly well-established that, in all but the most trivial of applications, unit testing will help to make your code more reliable. The cost of writing unit tests is the time it takes to write (and maintain) them. The cost of not writing them is the time it takes to fix the bugs that they miss.

Techniques such as Test-Driven Development (TDD) help to do this by incorporating testing as a first-class part of the design process. When you code using Test-Driven Development, you begin by writing unit tests to exercise your code, and then write the code to make the tests work. Writing the unit test helps (in fact, forces) you to think about the optimal design of your class _from the point of view of the rest of the application_. This is a subtle but significant shift in how you write your code.

However, when it comes to testing, developers are often at a loss as to what exactly should be tested. In addition, they tend to focus on the low-level mechanics of their unit tests, rather than

Behavior-Driven Development, or BDD, can provide some interesting strategies here. If you're not familiar with BDD, Andy Glover has written an excellent introduction here. Behavior-Driven Development takes Test-Driven Development (TDD) a step further. It is actually more a crystallization of good TDD-based practices, rather than a revolutionary new way of thought. Indeed, you may well be doing it already without realizing it. Using BDD, your tests help define how the system is supposed to behave as a whole. Using BDD, developers are encouraged to "What is the next most important thing the system doesn't yet do?" (see http://behaviour-driven.org/PowerfulQuestions).

This, in turn, leads to meaningful unit tests, with meaningful (albeit verbose) names, such as
shouldReturnZeroForIncomeBelowMinimumThreshold. For example, suppose you need to write a tax calculator. In the country in question, no tax is payable for incomes under a certain threshold, the exact value of which is determined by the laws of the land, and which, today, happens to be $5000.

Using a basic TDD approach, you might simply test directly against the $5000 value, as shown here:

@Test
public class TaxCalulatorTest {
    @Test
    public void testTaxCalculation() {

        double taxDueOnLowIncome = taxCalculator.calculateTax(4999);
assertThat(taxDueOnLowIncome, is(0.0));

        double taxDueOnHighIncome = taxCalculator.calculateTax(5000);
assertThat(taxDueOnHighIncome, greaterThan(0.0));
    }
}

However, at a more abstract level, this gets us thinking - where does this value come from? From a configuration file? A database? A web service? In any case, this is the sort of thing that can change at the whim of a politician, so it's probably not a good idea to hard-code it. So we should add a property to our TaxCalculator class to handle this parameter.

While we're at it, we rename the test to better reflect what behaviour we are trying to model. So, instead of talking about "testTaxCalculation" (where the emphasis is on what we are testing), we would use a name like "shouldReturnZeroForIncomeBelowMinimumThreshold". The use of the word "should" is deliberate - we are describing how the class should behave. Note how our intentions suddenly becomes clearer. The tests might now look like this:

@Test
public class TaxCalulatorTest {
    @Test
    public void shouldReturnZeroForIncomeBelowMinimumThreshold() {

taxCalculator.setMinimumThreshold(5000);
        double taxDueOnLowIncome = taxCalculator.calculateTax(4999);
assertThat(taxDueOnLowIncome, is(0.0));

        double taxDueOnHighIncome = taxCalculator.calculateTax(5000);
assertThat(taxDueOnHighIncome, greaterThan(0.0));
    }
}

These examples are a little contrived, and obviously incomplete, but the idea is there.

Tests written this way are a much clearer way of expressing your intent than with tests with names like "testCalculation1", "testCalulation2" and so on. In addition to the usual JUnit and TestNG, there are also some frameworks such as JBehave which make BDD even more natural. And, with tests like this, tools like TestDox (http://agiledox.sourceforge.net/) can be used to extract documentation describing the _intent_ of the classes. For example, for the test class above, TestDox would generate something like the following:

TaxCalulator
- should return zero for income below minimum threshold
...
Related Topics >>