Filed under: BDD, Cucumber, Test automation, — Tags: CXF, Cucumber-jvm, JSF, JUnit, Jersey, MVC, Model view controller, REST, RESTAssured, RESTFul, Selenium, Soap, Swing,, Swinggui, WebDriver, Wicket — Thomas Sundberg — 2012-11-01
The feature I will start with looks like this:
src/test/resources/se/waymark/rentit/Rent.feature
Feature: Rental cars should be possible to rent to gain revenue to the rental company. As an owner of a car rental company I want to make cars available for renting So I can make money Scenario: Find and rent a car Given there are 18 cars available for rental When I rent one Then there will only be 17 cars available for rental
It consists of three parts:
Next step is to connect the feature above with the System Under Test, SUT. A couple of things are needed.
The feature file is just plain text so parsing it is easy. Cucumber uses a parser called Gherkin to do this.
Cucumber then searches the class path to find any methods annotated with regular expressions that will match each
Given/When/Then
part of the feature. There must only be one method, step, which matches the regular
expression in the classpath. These methods are global. The reasoning behind making them global is that they describe
a part of the system. If two different parts are described the same way, then they are the same and the same step
definition can be used in both cases. If you have described two different parts of the system with the exact same
wording, then you have an issue with ambiguity. Take some time and understand why you describe different parts of a
system the same way. Chances are that they actually are the same thing and that you have some refactoring to do.
When the proper method has been located, it will be executed.
Cucumber can currently be executed using two different methods.
I choose to execute it using the JUnit runner. Connecting through JUnit will make it a seamless part of a project developed using tests. I don't have to supply the proper classpath that includes all features, all steps and the SUT. All I have to do is setup a Maven project where I locate the files according to the Maven standard and it just works.
The JUnit glue code is implemented as:
src/test/java/se/waymark/rentit/RunCukesTest.java
package se.waymark.rentit; import cucumber.api.junit.Cucumber; import org.junit.runner.RunWith; @RunWith(Cucumber.class) public class RunCukesTest { }
Naming the class *Test
will make Mavens Surefire plugin able to pick it up and execute it. Notice that
this is an empty class. This is by design. Cucumber demands that you implement the code that actually does
something, the steps, in separate classes in either the same package or a sub package.
The feature files must be located in the same package or a sub package, as the JUnit runner class is located.
The final thing needed to be able to execute the feature is to implement the steps that actually setup the system, execute it and finally assert its new state. The first iteration will be done like this:
src/test/java/se/waymark/rentit/steps/RentStepdefs.java
package se.waymark.rentit.steps; import cucumber.api.java.en.Given; import cucumber.api.java.en.Then; import cucumber.api.java.en.When; import cucumber.runtime.PendingException; public class RentStepdefs { @Given("^there are (\\d+) cars available for rental$") public void there_are_cars_available_for_rental(int availableCars) throws Throwable { // Express the Regexp above with the code you wish you had throw new PendingException(); } @When("^I rent one$") public void rent_one_car() throws Throwable { // Express the Regexp above with the code you wish you had throw new PendingException(); } @Then("^there will only be (\\d+) cars available for rental$") public void there_will_be_less_cars_available_for_rental(int expectedAvailableCars) throws Throwable { // Express the Regexp above with the code you wish you had throw new PendingException(); } }
This is a skeleton of the steps we want to execute on the system we are building. Before I move on, lets take a look at what we have.
Three methods. All three of them are annotated with @Given
, @When
and @Then
.
Each annotation has a string parameter. The string parameter is a regular expression that will be used when matching
the feature file with proper methods. Each group in the regular expression, indicated with a pair of parenthesis,
will be extracted as a parameter to the method. The group (\d)
would match a digit, (\d+)
any digits. Unfortunately we are dealing with Java here so '\' has to be escaped and the group for matching any
number of digits need to be expressed as (\\d+)
.
The step definitions above will not perform anything valuable. They are a starting point towards steps that actually do something that has a value. If you execute them, they will behave as an ignored unit test. Some text reminding you to implement them will be printed.
Now is a good time to implement the initial domain model needed for behaviour above. I will drive the initial implementation from the steps. As it looks now I will need
Hopefully this will be enough for this first iteration of the domain model.
Before I start implementing the model, I want to implement the steps that will verify the model. If I did it the other way around and started with the model and implemented the test steps afterwards it would not be driven by my need for a specific behaviour. Therefore I start with an implementation of the steps like this:
src/test/java/se/waymark/rentit/steps/RentStepdefs.java
package se.waymark.rentit.steps; import cucumber.api.java.en.Given; import cucumber.api.java.en.Then; import cucumber.api.java.en.When; import se.waymark.rentit.model.dao.CarDAO; import se.waymark.rentit.model.dao.InMemoryCarDAO; import se.waymark.rentit.model.entiy.Car; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; public class RentStepdefs { private CarDAO carDatabase; @Given("^there are (\\d+) cars available for rental$") public void there_are_cars_available_for_rental(int availableCars) throws Throwable { carDatabase = new InMemoryCarDAO(); for (int i = 0; i < availableCars; i++) { Car car = new Car(); carDatabase.add(car); } } @When("^I rent one$") public void rent_one_car() throws Throwable { Car car = carDatabase.findAvailableCar(); car.rent(); } @Then("^there will only be (\\d+) cars available for rental$") public void there_will_be_less_cars_available_for_rental(int expectedAvailableCars) throws Throwable { int actualAvailableCars = carDatabase.getNumberOfAvailableCars(); assertThat(actualAvailableCars, is(expectedAvailableCars)); } }
We recognise the three methods from earlier. The difference now is that they actually do something.
I am not really happy with the first method, it has a loop and I really don't like repetition in a test method. Repetitions add unnecessary complexity. I will take care of that a bit later.
The other thing we notice is that I have an instance of a data access object. It is a reference to the model that I use to store cars in and later retrieve cars from. I have to keep a state somewhere and an in-memory database is sufficient for the moment.
The remaining two methods should be more or less self-explaining. I reduce the number of available cars by renting one. I ask the system how many cars that are available and expect one less compared with when the system was setup.
The model I implemented to satisfy the steps above looks like this. I start with the data access object, CarDAO:
src/main/java/se/waymark/rentit/model/dao/CarDAO.java
package se.waymark.rentit.model.dao; import se.waymark.rentit.model.entiy.Car; public interface CarDAO { public void add(Car car); Car findAvailableCar(); int getNumberOfAvailableCars(); }
It needs a Car class to work with and it is implemented as:
src/main/java/se/waymark/rentit/model/entiy/Car.java
package se.waymark.rentit.model.entiy; public class Car { private boolean rented; public void rent() { rented = true; } public boolean isRented() { return rented; } }
This may be the simplest possible solution that could work. It is probably more or less useless in a production system. We might be interested in more things than if the car is rented or not. We might be interested in when a rental period started and when it ended, not just that the car is rented. But once again, this is a starting point and I want to keep the number of details as low as possible.
Finally I have implemented an in memory version of the CarDAO interface. It stores the cars in a list and iterates through the list for almost every operation. Not very efficient, but it is enough to start with. The implementation looks like this:
src/main/java/se/waymark/rentit/model/dao/InMemoryCarDAO.java
package se.waymark.rentit.model.dao; import se.waymark.rentit.model.entiy.Car; import java.util.LinkedList; import java.util.List; public class InMemoryCarDAO implements CarDAO { private static List<Car> cars; public InMemoryCarDAO() { if (cars == null) { cars = new LinkedList<Car>(); } } @Override public void add(Car car) { cars.add(car); } @Override public Car findAvailableCar() { for (Car car : cars) { if (!car.isRented()) { return car; } } throw new RuntimeException("No car available"); } @Override public int getNumberOfAvailableCars() { int availableCars = 0; for (Car car : cars) { if (!car.isRented()) { availableCars++; } } return availableCars; } }
Currently I have three layers. I have the feature, the specification or example, layer. Below that there is a layer of glue code that will connect the feature with the system under test. At the bottom I have a model. This is the production code that I actually might ship. This may or may not be enough, depending on your setup.
Different people are good at different things. There are programmers that really suck on testing and there are testers that really suck on programming. If we assume that the definition of the problem, the feature, belongs to the testers area of expertise and that the model belongs to the programmers area of expertise. How do we divide the problem so we don't force people that are good at writing code but bad at testing to do what they actually do best?
One way forward may be to simplify the api that the step definitions uses as much as possible. This would make the writing of the steps significantly easier. I need to introduce some help code that actually connects to the system under test to be able to do that. By doing that, however, I have the chance to divide the solution in two distinct parts that either belongs to the tester domain or that belong to the programmers domain. This division opens up for separating who actually implements each part and that is not something I actually think is good. I always think that it is a lot better if a tester and a programmer cooperate in finding a good solution.
Backed by this reasoning, I will introduce some help classes that will move the complexity from the steps and into a helper class instead. This will allow me to remove the repetition in the step above. It will also allow me to remove the reference to a domain object in the step definition.
My first step is to create a help class like this:
src/test/java/se/waymark/rentit/steps/RentACarSupport.java
package se.waymark.rentit.steps; import se.waymark.rentit.model.dao.CarDAO; import se.waymark.rentit.model.dao.InMemoryCarDAO; import se.waymark.rentit.model.entiy.Car; public class RentACarSupport { private CarDAO carDatabase; public void createCars(int availableCars) { carDatabase = new InMemoryCarDAO(); for (int i = 0; i < availableCars; i++) { Car car = new Car(); carDatabase.add(car); } } public void rentACar() { Car car = carDatabase.findAvailableCar(); car.rent(); } public int getAvailableNumberOfCars() { return carDatabase.getNumberOfAvailableCars(); } }
Naming utility classes like this is difficult. If you have any better suggestion for a name, please let me know.
The help class is obviously very similar to the old step definitions. It still contains a repetition. But now it belongs to the programmer's domain and repetition is ok here.
With the complexity moved, the new step definitions will look like this:
src/test/java/se/waymark/rentit/steps/RentStepdefs.java
package se.waymark.rentit.steps; import cucumber.api.java.en.Given; import cucumber.api.java.en.Then; import cucumber.api.java.en.When; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; public class RentStepdefs { private RentACarSupport rentACarSupport = new RentACarSupport(); @Given("^there are (\\d+) cars available for rental$") public void there_are_cars_available_for_rental(int availableCars) throws Throwable { rentACarSupport.createCars(availableCars); } @When("^I rent one$") public void rent_one_car() throws Throwable { rentACarSupport.rentACar(); } @Then("^there will only be (\\d+) cars available for rental$") public void there_will_be_less_cars_available_for_rental(int expectedAvailableCars) throws Throwable { int actualAvailableCars = rentACarSupport.getAvailableNumberOfCars(); assertThat(actualAvailableCars, is(expectedAvailableCars)); } }
The steps now consist of one and two line methods and the complexity is really low. This should be possible to read for both a tester and a programmer. A tester should be able to maintain and create new steps. They might want some initial assistance, but they should be able to add their own steps quite soon.
If more actions are needed on the system under test, they should be added in the proper help class. I would expect that a library of help classes eventually would emerge for a larger system than this small example.
The last thing needed to actually implement this example is a project file where the dependencies are defined. I use Maven and the Maven pom is defined as:
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project> <modelVersion>4.0.0</modelVersion> <groupId>se.waymark.educational</groupId> <artifactId>model</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> <scope>test</scope> </dependency> <dependency> <groupId>info.cukes</groupId> <artifactId>cucumber-java</artifactId> <version>1.1.1</version> <scope>test</scope> </dependency> <dependency> <groupId>info.cukes</groupId> <artifactId>cucumber-junit</artifactId> <version>1.1.1</version> <scope>test</scope> </dependency> </dependencies> </project>
The file organisation follows the normal Maven structure. I have a total of eight files in this example and they are located like this:
model |-- pom.xml `-- src |-- main | `-- java | `-- se | `-- waymark | `-- rentit | `-- model | |-- dao | | |-- CarDAO.java | | `-- InMemoryCarDAO.java | `-- entiy | `-- Car.java `-- test |-- java | `-- se | `-- waymark | `-- rentit | |-- RunCukesTest.java | `-- steps | |-- RentACarSupport.java | `-- RentStepdefs.java `-- resources `-- se `-- waymark `-- rentit `-- Rent.feature
All production code is located in 'main'. All test code, features, steps, help classes etc. are located in 'test'
This is all that is needed to use BDD and Cucumber to build a model that might the simplest possible solution. Next step is to use the same behaviour and add some user interfaces to it. I will build a web application in JSF.