This post has been migrated to http://www.thinkcode.se/blog/2012/09/28/test-drive-an-implementation-using-an-executable-specification-revisited
An example is perhaps the best way to describe something. Concrete examples are easier to understand than abstract descriptions.
I will show how Cucumber-JVM can be used to specify an example and how the example can be connected to the system under test, SUT. The example can then be executed using any IDE or using Maven.
The example I will use is about car maintenance. A car with an empty fuel tank need to be refueled. We will develop some code that will solve this problem. What we actually will implement is a simple adding function, but that is beside the point.
Before you start building the example, I need to show the file structure this example lives in. This is almost big design upfront, but hopefully you can bear with me while I do that.
example |-- acceptance | |-- pom.xml | `-- src | `-- test | |-- java | | `-- se | | `-- waymark | | `-- example | | |-- RunCukesTest.java | | `-- steps | | `-- FuelCarSteps.java | `-- resources | `-- se | `-- waymark | `-- example | `-- CarMaintenance.feature |-- pom.xml `-- product |-- pom.xml `-- src `-- main `-- java `-- se `-- waymark `-- example `-- Car.java
Given this file structure, you should be able to re-create this example.
Lets start with a feature. This is possibly the starting point for a very large project.
This feature is divided into two parts.
It starts with a headline and a description. This is background information. It tells us why we should bother creating this feature.
The introduction is followed by a scenario. A feature is made out of many scenarios. In this example there is just one scenario.
The scenario is then divided into three parts.
- The ‘Given’ part describes how the system should be prepared before the testing. This is the setup for the system.
- The ‘When’ part describes how the system should be used. This is the execution part of the example.
- The ‘Then’ part is where you verify that the system is in the expected state. This is the assert part of the test.
The feature above is not possible to run on its own. It need som glue to connect with the system that should be tested. The glue is divided in two parts. First there is a JUnit class that will connect the feature to a runner. It may look like this:
The test class is annotated with an annotation that defines that this class should be executed with a JUnit runner called ‘Cucumber.class’.
This annotation will enable us to execute this feature both in a IDE and with any build tool. With this in place, you can debug a single feature as well as enjoy continuous integration using tools like Maven. The JUnit runner will search for features in the classpath so it is important that they are located in the same package as the test class or in a subpackage to the test class.
Then there are the step definitions that actually connects the feature to the system under test. It may look like this:
It consists of three methods. One method for each step
- Setup the system under test
- Execute the implementation
- Validate the result
Every step definition is annotated with a regular expression. If the regular expression contains capture groups, a pair of parenthesis, the matches from these groups will be passed to the step definition method. The captured string will automatically be converted to the declared parameter type.
The step definitions and the test class must be separated. It means that there will be an empty test class. This is the desired behaviour of Cucumber.
With a feature and step definitions in place, it is time to develop the production code. This is the simplest solution that could work. It may even be seen as so small that it will not suffice. But it is sufficient for this starting point of our world domination product.
The final glue needed to be able to execute this example are three Maven poms. I use Maven to avoid all problems with downloading dependencies. Maven also integrates very well with the editor of my choice, IntelliJ IDEA.
Lets start from the top of our file hierarchy, the root pom for the entire example.
It connects the two modules and defines that JUnit should be available in every module during the test phase.
The next pom we need is the pom for the production code
It doesn’t do anything more than defines the module so it can be built.
The final pom that is needed is one that retrieves the cucumber dependencies and connects the production code to the acceptance tests.
It is not very difficult to implement an executable specification. We need a feature, some scenarios, step definitions and some wiring to connect the different parts. The feature is written using Gherkin, the step definitions and production code is written in Java and finally the wiring is done using Maven.
So Executable Specification or Specifications by Example isn’t rocket science, it is definitely doable and is a a great complement to the unit tests that you should use when developing the product.
This post has been reviewed by some people who I wish to thank for their help
- Malin Ekholm
- Johan Karlsson
- Aslak Hellesoy
Thank you very much for your feedback!
- Cucumber – a tool for executing specifications
- Gherkin – the language used to define the features
- Maven – a build tool
- Testing a web application with Selenium 2 – an example where Selenium and Cucumber are used together
- Test drive an implementation using an Executable Specification – the original post as presented at Agile By Example 2011
- Thomas Sundberg – The author