AS I mentioned at the end of this post, I’ve been convinced by Dan North’s case for using the “given when then” pattern for specifying scenarios during behaviour- or test-driven development, and while I wait for JBehave to be released, I’ve been playing around trying to come up with a way of using the pattern to clarify intent in (Java) unit/acceptance tests.
So far all I’ve got is a very simplistic and slightly messy spike, which I’ll describe here. I’ve deliberately avoided tying this in to a particular framework (I’ve used JUnit 4) or forcing test classes to extend a specific base class, so it should be equally applicable in TestNG, LiFT etc, and with other tools such as XMLUnit, EasyMock, JMock or Hamcrest.
There’s nothing clever going on here, and it’s far from the elegance of something like rspec – partly because of Java lack of things like closures, and partly because of my limited talents.
For this example, I’ve used a few trivial tests for the Stack
class. I’m going to use a single given (the precondition or setup for a test or set of tests), a couple of whens (events whose effect you’re testing/specifying) and several thens (assertions/specifications of expected behaviour). Because the setup is part of the given, I’m not using JUnit’s built-in test setup capability. I’ll show the example first, then the code behind the scenes. If you like, the whole thing (such as it is) is available as givenwhenthen.zip.
Example
First, here’s the basic test class, to show there’s nothing up my sleeves:
package stack; import static givenwhenthen.Given.given; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import givenwhenthen.Given; import givenwhenthen.When; import java.util.EmptyStackException; import java.util.Stack; import org.junit.Test; public class StackSpecs { protected Stackstack; ... }
Now the given (an newly-created Stack
object):
Given aNewStack = new Given("a new stack") { @Override public void execute() { stack = new Stack(); } };
OK, I already warned you it was a bit messy. Now some whens, specifying things that can happen to the new stack:
When anItemAdded = new When("an item is pushed") { @Override public void execute() { stack.push("foo"); } }; private When pop = new When("an item is popped") { @Override public void execute() { stack.pop(); } };
And finally the important bit – the thens containing the assertions:
@Test public void stackShouldNotBeEmptyAfterPush() { given(aNewStack).when(anItemAdded).then("the stack should not be empty"); assertFalse(stack.isEmpty()); } @Test public void stackShouldHaveASizeOf1AfterPush() { given(aNewStack).when(anItemAdded).then( "the stack should have a size of 1"); assertEquals(1, stack.size()); } @Test public void newStackShouldBeEmpty() { // it works with no 'when' too! given(aNewStack).then("the stack should be empty"); assertTrue(stack.isEmpty()); } @Test public void newStackShouldHaveASizeOf0() { given(aNewStack).then("the stack should have a size of 0"); assertEquals(0, stack.size()); } @Test(expected = EmptyStackException.class) public void poppingFromNewStackShouldFail() { given(aNewStack).when(pop).then( "an EmptyStackException should be thrown"); }
These are hopefully relatively readable.
Implementation
The code to support this is contained in two small classes: Given
and When
.
Here they are, with hopefully enough comments to explain how they work (it’s hardly rocket science anyway!):
package givenwhenthen; /** * Create an anonymous subclass of this class for each 'given' instance. * * @author Kerry Buckley */ public abstract class Given { private String desc; public Given(final String desc) { this.desc = desc; } /** * This method must be implemented to perform the behaviour corresponding to * the particular 'given' instance. */ public abstract void execute(); /** * Runs a 'given' instance. Intended to be statically imported so the test * can use it as follows: * ** given(aGivenInstance).when(aWhenInstance).then("something should happen"); **
* @param given
* the instance to run
* @return the same instance which was passed in
*/
public static Given given(final Given given) {
given.report();
given.execute();
return given;
}/**
* Runs a 'when' instance. Intended to be statically imported so the test
* can use it as follows:
*
** given(aGivenInstance).when(aWhenInstance).then("something should happen"); **
* This is here rather than in {@link When} to allow the chaining described
* above. Defers throwing exceptions to allow the complete given-when-then
* to be reported in the case where an exception is expected (@see
* {@link When#then(String)}).
*
* @param when
* the instance to run
* @return the same instance which was passed in
*/
public When when(final When when) {
try {
when.execute();
} catch (RuntimeException t) {
when.setThrown(t);
}
return when;
}/**
* Does not participate functionally in the test, but allows the 'then'
* description to be reported. Exists in this class as well as in
* {@link When} to allow for cases where no 'when' is required.
*
* @param then
* a descriptive string
*/
public void then(String then) {
When.reportThen(then);
}private void report() {
System.out.println("\nGiven: " + desc);}
}package givenwhenthen; /** * Create an anonymous subclass of this class for each 'when' instance. * * @author Kerry Buckley */ public abstract class When { private String desc; private RuntimeException thrown; public When(final String desc) { this.desc = desc; } /** * This method must be implemented to perform the behaviour corresponding to * the particular 'when' instance. */ public abstract void execute(); /** * Does not participate functionally in the test, but allows the 'when' and * 'then' descriptions to be reported. Also throws any exceptions deferred * in {@link Given#given(Given)}. * * @param then * a descriptive string */ public void then(final String then) { report(); reportThen(then); if (thrown != null) { throw thrown; } } public void setThrown(final RuntimeException t) { this.thrown = t; } static void reportThen(final String then) { System.out.println("Then: " + then); } private void report() { System.out.println("When: " + desc); } }Reporting
The classes above generate a report of the specifications. For the spike, this is just spewed out to the console as plain text, but a real implementation could use any destination or format.
Here’s the output from the example tests:
Given: a new stack When: an item is pushed Then: the stack should not be empty Given: a new stack When: an item is pushed Then: the stack should have a size of 1 Given: a new stack Then: the stack should be empty Given: a new stack Then: the stack should have a size of 0 Given: a new stack When: an item is popped Then: an EmptyStackException should be thrownLimitations
Obviously there are many! Off the top of my head:
- It’s not particularly elegant (especially the given and when declarations)
- There’s duplication between variable/method names and the descriptive strings
- The spec list doesn’t highlight failures
Any comments or suggestions welcome!
[tags]tdd, bdd, agile, junit,testing[/tags]