Wednesday, August 25, 2010

Minimalist groovy BDD framework, from executable documentation to readable tests

In many projects, my target customers are other developers, not an end user that write prose. I am a developer. I don't write documentation, I write code, documented code.

When it comes to testing, I like the BDD approach. I've tested various BDD frameworks (easyb, cucumber, spock...). But I wasn't completely satisfied for various reasons (setup complexity, issues, maintainability, toolchain).

So I've gone back from executable documentation to readable tests. And I have settled on my own framework. Framework is a big name, you will see. In combination with mockito and a few coding conventions, it's the 1% that give me the 99% that I am looking for.




/**
* Simple Groovy BDD framework.
*
* To use with static imports. See BDDTest.groovy code for example
*
* We prefix method names with underscores characters to not conflict with things like Mockito.
*
* @author jerome@coffeebreaks.org
*/
class BDD {
static def _given(Closure block) {
block.run()
}

static def _when(Closure block) {
block.run()
}

static def _then(Closure block) {
block.run()
}
}


That's it.

Example usage:


class Doer {
def Listener listener;

def hello(name) {
listener.notify("hello " + name);
}
}
interface Listener {
def notify(String msg)
}


The tests themselves

import static BDD.*
import static org.mockito.Mockito.*
import org.junit.Test

/**
* Example test using the simple BDD framework
*
* @author jerome@coffeebreaks.org
*/
public class BDDTest {
def sum(a, b) {
return a + b
}

@Test
void summing() {
def a, b, s
_given {
a = 1
b = 1
}
_when {
s = sum(a, b)
}
_then {
assert s == 2
}
}

@Test
void mockitoIntegration() {
def listener, doer
_given {
listener = mock(Listener)
doer = new Doer()
doer.listener = listener
}
_when {
doer.hello("bdd")
}
_then {
verify(listener).notify("hello bdd")
}
}
}


When I have complex scenarios, e.g. for acceptance test, I write the scenarios using camel case methods and a base class for set up tear down

E.g.

class SystemStartupTest extends AbstractSystemTest {
@Test
void systemStartedInXModeDisplaysProperGUI() {
_given {
aSystemConfiguredWithOptions(Mode.X, [Options.A, Options.C]
}
_when {
systemStarts()
}
_then {
theAPanelShouldBeVisible()
theXButtonShouldBeActivated()
theXTableShouldBeFocused()
}
}
void theXTableShouldBeFocused() {
assert system.getView().getXTable().hasFocus()
}
[...]
}



Benefits:

  • the setup complexity is ridiculous simple, no special build and IDE plugin, no special syntax, etc
  • I can reorganize my code the way I want and refactor it at will (extract methods...)
  • I stick to known frameworks (JUnit, Groovy assert, mockito) and coding syntax
  • I don't have text -> code mapping to maintain. Maintenance is bloody easy


Drawbacks:

  • I don't have nice colored reports when I have a failure (which I don't necessarily care of as long as I have my stack trace...)


Some might say it's not readable, that the GWT blocks pollute the code, that the camel case methods aren't practical to read, and sometimes have to be expressed differently as the arguments are postfixed.

But I am used to these limitations, I live in them since I write code, and I will be the one maintaining the code anyway.

So this might not be for you. But for me, so far, on selected projects, it works.

Ask yourself: what is it you can't leave without from your BDD framework ? If you were to have a minimalist BDD framework, what would you have in it ?

For me it wasn't the framework itself, it was the process, the communication that was enforced upfront. And this doesn't depend on your framework.

No comments:

Post a Comment