In my previous column, I discussed functional TDD, which is a form of TDD I am cottoning to. The basic difference when compared with unit test-based TDD is that the functional test serves as the driver of code. I write a failing functional test that defines my next coding effort. When the test passes, I either write another, or, more often, I’ll write unit tests to exercise edge conditions on the code I’ve just created. This approach confers many of the same benefits as traditional TDD, while mitigating some of its shortcomings. I discussed some of these in the previous column and promised I’d discuss the tools I use here.
I write functional tests in the two fundamental coding scenarios: prior to writing new code, and when maintaining existing code. Because I code primarily in Java, I’ll look at the tools that work with it.
When fixing defective code, I reproduce the problem using Groovy as a scripting language. Groovy scripts are easy to hack together quickly, and the syntax is close enough to Java that its use is not an obstacle to other developers on the team. They can understand the tests easily. I try to reproduce the defect at the highest level possible. An ideal result is running the entire application and causing the error to show. Then, I’m really testing how the solution I propose works with the various computational units it deals with in deployment, rather than testing it in isolation.
These functional tests go into a regression suite, and the entire suite is run before the defect is closed to make sure all is copasetic. Once the functional test passes, I look for edge conditions and code that was insufficiently tested. And for items, I write either additional functional tests or, more commonly, unit tests. The choice depends on the locus and scope of the tests.
When it comes to writing new code, I rely on tools normally associated with behavior-driven development (BDD). This approach favors writing code scenarios from requirements in the form of tests, and then implementing the scenarios in code. Typically, the scenarios have the form of: given X and Y, when A occurs, B should result. The framework then sets up X and Y, does A, and tests for B.
There are two BDD frameworks that I know of that do this well in the Java universe: easyb, which won a Jolt award last year, and JBehave. Many BDD-oriented frameworks are appearing for other languages, including Cucumber, RSpec and ScalaTest, and they’re gaining popularity.
The scenario-based approach of BDD does not inherently define the scope of tests. It can be used at a high level where the test incarnates the project requirements. In theory, if all scenarios pass, the project is complete. They can also dip down to unit and sub-unit levels.
The JUnit style of testing with set-ups and tear-downs and frequent setting of mocks to create an environment where a specific condition can be tested is not that different from a “given X, given Y” scenario. For my purposes, I use BDD for a level that falls between these two endpoints: something greater than a unit but less than the full program, which (crucially!) articulates a scenario the user would recognize. I attempt to stay above implementation details in the scenarios.