This post has been migrated to http://www.thinkcode.se/blog/2014/10/23/an-email-marketing-system-built-using-test-first-and-cucumberjvm
It is a (baby) step by step tutorial. The purpose for me taking baby steps is that you should be able to follow and implement the same things. Be prepared to spend some time with the implementation, it will probably take you a few hours.
Before we dive into the example, let me define what I am aiming for. My goal is to show you how an example (or specification if you want) can be executed. The example is written in plain text and is used to automate the testing of the system I will create. These executable examples can later be relied upon for regression testing and a living documentation.
So what am I about to implement?
I plan to implement a service where people can subscribe to marketing messages. Some people might call it a spam service. The implementation will be enough for us to get started with Behaviour Driven Development, BDD. The purpose is to allow me to use Cucumber-JVM to define some requirements for the system and use these requirements to drive the implementation.
The implementation will be done using test first and I will allow the system to grow in small steps from the outside in.
With this small background I think it’s time to get started.
Let us start with creating a directory where the example will live. I will just create something called
Create a Maven project file, pom.xml, in the root of the directory you just created. This tutorial is about Cucumber-JVM so I will not go through all details in this project file.
Add this content to our new file
What did I just do? I defined that the organisation that implements this example is called
se.thinkcode. I also defined that the project is called
cucumber-jdd-2014 and that the version is
Then there are three dependencies defined. These are three libraries we want to have access to so we can use Cucumber. They will only be used from the tests so I defined them in the test scope.
Try to run Maven with this project file. Execute the command
mvn clean install
in the same directory as the
The result should be that you downloaded half of the Internet. The good thing with this is that this part of the Internet contains all the things we need to be able to create a few features with Cucumber and the necessary tools for running them.
We need two things to be able to run Cucumber. We need a runner and we need features to execute. Let me start by implementing the runner.
Cucumber can be executed using JUnit through a specific JUnit runner. I will call the class that will execute Cucumber
I am using Maven to build this project so I will stick to the Maven convention and define this test class in the test class hierarchy. Let me create the directory
src/test/java/se/thinkcode/jdd/ sand then create the class
RunCukesTest in the package
It is important that the class name ends with
Test. This will allow the Maven test runner to find it and execute it. This will in turn allow us to execute Cucumber as part of a normal Maven build. That is, similar a build to the one you just executed.
A test class will, however, not be able to do anything with Cucumber unless it is executed with a specific Cucumber runner. Let me add the Cucumber runner to the test class with the annotation
@RunWith(Cucumber.class) above the class definition.
The class should now look like this:
This is all we need to be able to run Cucumber. You can add more annotations that will give you more functionality. I will not need any more so I will not add anything extra and instead keep the example as small and simple as possible.
Lets run the build again and see that I haven’t introduced anything strange.
mvn clean install
The interesting part of the execution should look something like this:
No tests were executed and no features were found. That was expected.
This is our next task. Add a few features that will define our wanted behaviour. The nice thing is that they will be possible to execute. We will always know if the wanted behaviour is available or not. This is nice during the development and it is invaluable during the rest of the systems life.
Where should the feature be defined and what should they look like?
Cucumber is searching the class path for files with the suffix
feature. The features must be located in the same package or a subpackage below the package where the runner is located. In our case, the package
se.thinkcode.jdd or a subpackage.
The convention in Maven is that anything found in a directory called resources at the same level as the java directory I created earlier will be a part of the classpath. In other words, the features should be defined in
So we know where the feature should be located. The next question is, what should it contain? This is a very important question and the answer should come from our stakeholders. Or at least from a conversation you, or someone else, have had with the stakeholders.
Remember, Cucumber is first and foremost a conversation tool. It should be used to capture and document conversations that will define what the wanted behaviours of your project is. The technical part of Cucumber, the part I am implementing here, is not the most important part of Cucumber. The most important part is the conversations that must take place before we can implement something that our users need and want.
With this in mind, let me define two things our email marketing tool should support.
- New subscribers should be able to subscribe to messages with a valid e-mail addresses
- Registered subscribes should be able to unsubscribe from the service
These are the two first requirements we will implement. Notice that sending marketing e-mails is not part of the list yet. This is obviously strange, but it is a strange world we are living in. And this is just an example where I will be able to show you how to use Cucumber.
The first feature will be called subscribe.feature. Let’s create the file and add this content to it:
What are looking at here? This is an example of the language Gherkin. Gherkin is a formal language that is used by Cucumber to specify scenarios that defines the wanted behaviour. There are five keywords defined in this example.
- Feature – a short description of the feature. Try to express yourself in one sentence
- Scenario – the actual scenario that should be working
- Given – the setup step. This is where you define the preconditions for the wanted behaviour
- When – the execution step. This is where you use the system in some way
- Then – the assertion step. This is where you observe the system and assert that the wanted change has occurred
You can add whatever additional explanation you want between the Feature and the first Scenario. I added a user story that describes the business value that this feature should bring to the system. You may think that the format is a bit unusual. This is a format suggested by Liz Keogh that tries to emphasise the business value of the feature. It doesn’t just say As a xxx, I want yyy, So that zzz. It tries to say that in order to achieve something (useful) as a role, I must do something.
The format of the user story really doesn’t matter to Cucumber. It may, however, matter a lot on how you understand the wanted behaviour and therefore how you understand the business value that will be added when you implement this story. And remember, if there isn’t a business value in the other end, ask why you should implement this in the first place.
Ok, so this was short on the feature file. Let us see if we can use it. Let us build the system with Maven.
mvn clean install
That went pretty well, we didn’t have a build failure. But if we look at the stuff logged we will see that there are issues. Cucumber can’t find any code corresponding to the steps I defined in the
There are, however, some suggestions that we could use to get started. Copy these suggestions and use them as a starting point when implementing the methods that actually will be executed.
But where should these methods be implemented? There is a simple answer, in any class in the same package as the test class or a subpackage.
All steps that we implement will be global from the point of Cucumber. We can, and probably should, group the steps in classes that are coherent. But each step is global and can be reused in different scenarios.
Global definitions may seem like something very bad and possibly stupid. There is, however, a simple and good explanation. The steps describe a specific behaviour of your system. One step correlates to a description of a desired behaviour. If you describe two different parts of your system with the exact same words, you have a problem. A much bigger problem than global steps. The global steps will help you find your blunder since it will use the specified implementation and that implementation may not do what you actually wanted. In a sense, Cucumber will behave as a whistle blower that tells you that your specification is ambiguous.
Ok, back to our implementation.
I copied the suggested steps. Now, let me create a class where these steps can live. I like to abstract away details that are less important so I will hide these steps in a subpackage to
se.thinkcode.jdd that I call
I will call the class where these steps will live
SubscribeSteps and paste the suggested steps into it.
These classes need to be imported:
We should be able to build the project again. Run the Maven build.
mvn clean install
It was possible to build but, looking at the output, we got some information about things that should be implemented.
These comments comes from the
PendingException that is thrown in every step. We don’t really have any reason to be surprised. The snippets we used clearly throws them. The code also contains these comments:
// Write code here that turns the phrase above into concrete actions
These comments may actually mean something…
Let us examine the suggested steps a bit closer. We have a code that will be executed whenever we find the Given, When och Then steps. Each method is annotated with a regular expression. These regular expressions will act as the glue between our scenarios and the code.
We would like to use the e-mail address as a parameter in some of the steps. Collecting parameters are done using groups in the regular expression. A group is the stuff you find within two parenthesis.
Let us take a look at the the first scenario and see what we actually should make sure works.
Given I want to subscribe to receive important market information
This just tells me that the system should be ready to accept subscriptions. This is the step where the system should be started so it can respond to the next steps.
The next step is
When I enter a valid address email@example.com
I would like to catch the e-mail address here. I will do that by modifying the regular expression so it looks like this instead:
@When("^I enter a valid address (.*)$")
I replaced ‘firstname.lastname@example.org’ with the group (.*) This group will catch any string at that position in the requirement.
The result should then be passed to the method.
In order to do that, I will add a parameter to the method call. A String with a good name will be sufficient for now. The result should look something like this:
The address parameter will contain the e-mail address supplied.
The final step is
Then should I get a welcome message
This should assert that a message has been set and that it is some kind of greeting to the user that just subscribed.
Now when we understand how the methods are connected to the scenario, let me create a first implementation.
I often try to postpone implementing actual production code as long as possible. I will use a style that sometimes is called TDD as if you meant it in this example. This means that I will try to get this first example to pass with the smallest possible implementation that could work.
I think we can get away with validating the e-mail address and creating a greeting message in this case. Let’s start there. Let me set a message in the first method where the e-mail address is supplied. It could look like this:
message = "Welcome " + address;
Message has to be an instance variable so it can be verified later.
Next thing would be to validate that the message was set as we wanted. Implement this in the method matching the Then step. A simple assertTrue will be sufficient. Let me implement it as:
assertTrue("The welcome message should contain 'Welcome' and it was <" + message + ">", message.contains("Welcome"));
This is obviously just a strange implementation. But it is small steps and I am headed in a direction. It might actually be a good direction. Just follow me and I hope you will understand where.
This code will unfortunately never be executed. The reason is that the first method still throws a PendingException. Let me remove it.
This means that the method executed in the Given step will be empty, but that is ok for now. There will a reasonable implementation there soon.
Lets build again and see what happens.
We have a successful build and just one ‘TODO: implement me’ left. This one is from the pending exception for an invalid e-mail address. Let me do something similar as with the other Given method and see if I can remove all PendingExceptions. I want to catch an invalid e-mail address and set some kind of error message. Let me implement something very simple, let me just set the message.
message = "There was an error with the address " + address;
This implementation needs the address so you must get it as a parameter to the method. Use the same technique as above and add a group in the regular expression and make it available to the method through a String parameter called address.
I also need to implement some kind of assert. Being the laziest person in the world, I will implement something like this in the last then step:
assertTrue("The error message should contain 'error' and it was <" + message + ">", message.contains("error"));
Build again and see what happens.
We got a successful build and no ‘TODO: implement me’ is left. This is great, are we done now? Nope. We have faked everything and we need to add a proper implementation.
The only reasonable thing to implement at this stage is a method that validates the e-mail addresses and sets the message. A simple implementation would be:
This method should be called whenever we should set the message. Change the implementation in the two Given steps to something like this:
Build this and see what happens. I don’t expect any more issues.
This system is quite useless as it is at the moment, but it seems to work as we have defined that it should work. What is a reasonable next step? I think it is to create a subscription class where we can store subscriptions and get the expected messages back when we try to subscribe with a valid and with an invalid e-mail address.
Creating this Subscription could be done in the the Given method. This would set up the system under test.
Let me create a class in
that I call
Subscriptions. I think it should have one method,
subscribe. This method could accept the e-mail address and return a status message. Validation seems to fit better in the subscription class than in the step class so I will move it there.
I will do the validation when I add a new subscriber. This means that I have to change when I use the system, the When part. Instead of validating the e-mail, I will add the subscription and examine the result of adding a subscriber. The steps implementation should now look like this:
The implementation of
Subscriptions could look like this:
This is as far as this first requirement seems to take me now. The only things I check for is that I will get an error message with a broken e-mail address and and a welcome message with a valid e-mail address. And these validations are done in the subscribe method. It is indeed a strange world. I need to add some requirement to be able to move along. Fortunately, the unsubscribe feature will do just that.
Let me add a feature for unsubscribing and see where it takes us. Create a new feature called unsubscribe.feature with this content:
What do we have here? More of the same? To some extent, but not only. We have one new keyword,
And. It behaves in a similar way as Given/When/Then. The sentence will be used to look up the proper method and execute it.
With these added requirements, I will be able to subscribe and unsubscribe. This will force me to implement persistence. Lets start by running this and see what steps are missing.
mvn clean install
It looks like I am missing these steps:
Ok, let me implement some unsubscribe steps and see where they leads us. I will add the class
UnsubscribeSteps and add the method snippets to it.
Interestingly, there is a method duplication here. Unsubscribing a valid and invalid address generated the same method snippet. The reason why two similar steps were suggested is that the regular expressions used to locate them are equal. Cucumber will therefore suggest two methods with the same name. Two methods with the same name is clearly illegal in Java so one of them must be removed.
The e-mail address must be captured and used it in some of the steps. The technique is the same as previously, add a regular expression group and add it as a parameter to the method.
The Given method must be implemented. I will create a
Subscriptions instance and subscribe with the e-mail address supplied. It could look something like this:
subscriptions = new Subscriptions(); subscriptions.subscribe(address);
Building this results in two ‘TODO: implement me’. They comes from the
PendingException that is being thrown. Let me implement the support I need in Subscriptions. It should really just be unsubscribe as far as I can understand at the moment. I will implement the unsubscribe in the When step. A reasonable implementation is this:
A successful unsubscription should should result in a message that contains ‘Goodbye’. Let me assert that in the Then method.
assertTrue("The unsubscribe message should contain 'Goodbye' and it was <" + message + ">", message.contains("Goodbye"));
How can I check that a subscriber is unsubscribed? I seem to need another method,
isSubscriber, so I can verify that. Let me add it to Subscriptions. It should return true if the subscribers e-mail address is a subscriber and false if it isn’t. All I need is an assert in the method called in the And step. Note that the And step is annotated as a Then step. It turns out that Cucumber don’t really care about the annotation. Cucumber doesn’t separate Given/When/Then/And/But, they are all just annotations used to to locate a method to run. The words only matters in Gherkin and they are only there to make it easier to read.
The assertion for checking if an e-mail is a subscriber or not may look like this:
assertFalse(address + " should not be subscribing", subscriptions.isSubscribing(address));
Before I build this, let me see if I can fix the two remaining methods.
Lets start with the message when the unsubscribe failed because the unsubscribed e-mail address wasn’t valid. The assert may look like this:
assertTrue("Unsubscription failed with the message <" + message + ">", message.contains("Not unsubscribed"));
The last assert is that the subscriber should still be a subscriber when unsubscribe failed. This means assert to true when checking if an address is a subscriber.
assertTrue(address + " should be a subscriber", subscriptions.isSubscribing(address));
The implementation of the steps should look something similar to this now:
Build this and see what happens.
It failed, unsubscribe has not been properly implemented.
What do I need to do to unsubscribe someone? I think I will need to remove this subscriber from some list. But, I haven’t actually added any subscribers to a list. So I have two things to do. I need to add subscribers to a set when they subscribe and I need to remove them from the set when they unsubscribe. I will use a set instead of a list. A set only allow one of each address so each address will therefore be unique.
Before I do that, let me make sure that the message from unsubscribe is returned properly. Building the system tells me that I need to return “Goodbye” when someone is unsubscribing. Let me hard code that and see what happens.
message = "Goodbye " + address;
Ok, looking at the result tells me that hard coding Goodbye wasn’t a great idea. It will only solve the happy path, I need to handle the option when an unsubscription failed as well. Let me implement adding, remove and checking as properly as I can do it now.
Start by adding a place where subscriptions can be stored. Let me add an instance variable that
can hold subscribers,
private Set<String> subscribers;
The next steps are to add a subscriber, remove and check if it subscribing or not. The final implementation could look something like this now:
There are some strange things going on here. It is strange that the validation method returns a welcome message. It would be more reasonable if it returned true or false. Another strange thing is that I treat e-mail addresses as a String. It isn’t a String, it is an e-mail address.
Unfortunately, this is as far as I got during the tutorial at JDD. I had planned to introduce a domain object
EmailAddress so I could stop sending around a String when I actually mean an e-mail address. The
EmailAddress would also have a static method for validating that a string is a valid e-mail address. But due to time limits, I will have to leave that as an exercise to you, the reader. I do, however, expect that you will be able to extend the example so it don’t uses a String for representing an e-mail address if you have been able to follow along to this point.
This is a small example. It should be small enough so you are able to implement it without getting lost among the details. It is at the same time large enough to show how you can execute a plain text example.