An email marketing system built using test first and Cucumber-JVM

Filed under: Cucumber, Java, — Tags: BDD, Cucumber-jvm, Executable specification, JDD 2014, Java Developer Day 2014, Living documentation, Test automation — Thomas Sundberg — 2014-10-23

This is the example I implemented at JDD 2014 in Krakow, Poland at my Cucumber-JVM tutorial.

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.

I will use Maven and Java in this example. If you don't have them installed, install them before you continue.

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 EmailMarketer somewhere.

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 pom.xml:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>se.thinkcode</groupId>
    <artifactId>cucumber-jdd-2014</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-java</artifactId>
            <version>1.1.8</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-junit</artifactId>
            <version>1.1.8</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

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 1.0-SNAPSHOT.

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 pom.xml.

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 RunCukeTest

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 se.thinkcode.jdd.

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:

src/test/java/se/thinkcode/jdd/RunCukesTest.java

package se.thinkcode.jdd;

import cucumber.api.junit.Cucumber;
import org.junit.runner.RunWith;

@RunWith(Cucumber.class)
public class RunCukesTest {
}

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:

-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running se.thinkcode.jdd.RunCukesTest
No features found at [classpath:se/thinkcode/jdd]

0 Scenarios
0 Steps
0m0.000s

Tests run: 0, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.367 sec

Results :

Tests run: 0, Failures: 0, Errors: 0, Skipped: 0

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 src/test/resources/se/thinkcode/jdd

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.

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:

src/test/resources/se/thinkcode/jdd/subscribe.feature

Feature: Subscribe for our e-mail marketing service

  In order to be able to receive important marketing messages
  As marketing message receiver
  I should be able to register my e-mail address

  Scenario: Subscribe with a valid e-mail address
    Given I want to subscribe to receive important market information
    When I enter a valid address tsu@kth.se
    Then should I get a welcome message

  Scenario: Try to subscribe with an invalid e-mail address
    Given I want to subscribe to receive important market information
    When I enter an invalid address tsu.kth.se
    Then should I get an error message

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.

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 subscribe.feature.

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.

You can implement missing steps with the snippets below:

@Given("^I want to subscribe to receive important market information$")
public void i_want_to_subscribe_to_receive_important_market_information() throws Throwable {
    // Write code here that turns the phrase above into concrete actions
    throw new PendingException();
}

@When("^I enter a valid address tsu@kth\\.se$")
public void i_enter_a_valid_address_tsu_kth_se() throws Throwable {
    // Write code here that turns the phrase above into concrete actions
    throw new PendingException();
}

@Then("^should I get a welcome message$")
public void should_I_get_a_welcome_message() throws Throwable {
    // Write code here that turns the phrase above into concrete actions
    throw new PendingException();
}

@When("^I enter an invalid address tsu\\.kth\\.se$")
public void i_enter_an_invalid_address_tsu_kth_se() throws Throwable {
    // Write code here that turns the phrase above into concrete actions
    throw new PendingException();
}

@Then("^should I get an error message$")
public void should_I_get_an_error_message() throws Throwable {
    // Write code here that turns the phrase above into concrete actions
    throw new PendingException();
}

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 steps.

I will call the class where these steps will live SubscribeSteps and paste the suggested steps into it.

These classes need to be imported:

import cucumber.api.PendingException;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;

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.

cucumber.api.PendingException: TODO: implement me

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 tsu@kth.se

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 'tsu@kth.se' 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:

@When("^I enter a valid address (.*)$")
public void i_enter_a_valid_address_tsu_kth_se(String address) throws Throwable {

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:

private void validateEmail(String address){
    if (address.contains("@")) {
        message = "Welcome " + address;
    } else {
        message = "There was an error with the address " + address;
    }
}

This method should be called whenever we should set the message. Change the implementation in the two Given steps to something like this:

validateEmail(address);

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

src/main/java/se/thinkcode/jdd/

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:

package se.thinkcode.jdd.steps;

import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
import se.thinkcode.jdd.Subscriptions;

import static org.junit.Assert.assertTrue;

public class SubscribeSteps {
    private String message;
    private Subscriptions subscriptions;

    @Given("^I want to subscribe to receive important market information$")
    public void i_want_to_subscribe_to_receive_important_market_information() throws Throwable {
        subscriptions = new Subscriptions();
    }

    @When("^I enter a valid address (.*)$")
    public void i_enter_a_valid_address_tsu_kth_se(String address) throws Throwable {
        message = subscriptions.subscribe(address);
    }

    @Then("^should I get a welcome message$")
    public void should_I_get_a_welcome_message() throws Throwable {
        assertTrue("The welcome message should contain 'Welcome' and it was <" + message + ">", message.contains("Welcome"));
    }

    @When("^I enter an invalid address (.*)$")
    public void i_enter_an_invalid_address_tsu_kth_se(String address) throws Throwable {
        message = subscriptions.subscribe(address);
    }

    @Then("^should I get an error message$")
    public void should_I_get_an_error_message() throws Throwable {
        assertTrue("The error message should contain 'error' and it was <" + message + ">", message.contains("error"));
    }
}

The implementation of Subscriptions could look like this:

package se.thinkcode.jdd;

public class Subscriptions {
    public String subscribe(String address) {
        if (address.contains("@")) {
            return "Welcome " + address;
        } else {
            return "There was an error with the address " + address;
        }
    }
}

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:

src/test/resources/se/thinkcode/jdd/unsubscribe.feature

Feature: Unsubscribe for our e-mail marketing service

  In order to be a good marketing service, we want to allow our subscribers to unsubscribe from our messages
  As marketing message receiver
  I should be able to unregister my e-mail address

  Scenario: Unsubscribe from marketing messages with a valid e-mail address
    Given I have subscribed for a marketing service with my e-mail tsu@kth.se
    When I unsubscribe tsu@kth.se
    Then I should get a goodbye message
    And tsu@kth.se should be unsubscribed

  Scenario: Unsubscribe from marketing messages with an invalid e-mail address
    Given I have subscribed for a marketing service with my e-mail tsu@kth.se
    When I unsubscribe tsu.kth.se
    Then I should get an error message
    And tsu@kth.se should not be unsubscribed

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:

@Given("^I have subscribed for a marketing service with my e-mail tsu@kth\\.se$")
public void i_have_subscribed_for_a_marketing_service_with_my_e_mail_tsu_kth_se() throws Throwable {
    // Write code here that turns the phrase above into concrete actions
    throw new PendingException();
}

@When("^I unsubscribe tsu@kth\\.se$")
public void i_unsubscribe_tsu_kth_se() throws Throwable {
    // Write code here that turns the phrase above into concrete actions
    throw new PendingException();
}

@Then("^I should get a goodbye message$")
public void i_should_get_a_goodbye_message() throws Throwable {
    // Write code here that turns the phrase above into concrete actions
    throw new PendingException();
}

@Then("^tsu@kth\\.se should be unsubscribed$")
public void tsu_kth_se_should_be_unsubscribed() throws Throwable {
    // Write code here that turns the phrase above into concrete actions
    throw new PendingException();
}

@When("^I unsubscribe tsu\\.kth\\.se$")
public void i_unsubscribe_tsu_kth_se() throws Throwable {
    // Write code here that turns the phrase above into concrete actions
    throw new PendingException();
}

@Then("^I should get an error message$")
public void i_should_get_an_error_message() throws Throwable {
    // Write code here that turns the phrase above into concrete actions
    throw new PendingException();
}

@Then("^tsu@kth\\.se should not be unsubscribed$")
public void tsu_kth_se_should_not_be_unsubscribed() throws Throwable {
    // Write code here that turns the phrase above into concrete actions
    throw new PendingException();
}

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:

subscriptions.unsbscribe(address);

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:

package se.thinkcode.jdd.steps;

import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
import se.thinkcode.jdd.Subscriptions;

import static org.junit.Assert.assertTrue;

public class UnsubscribeSteps {
    private String message;
    private Subscriptions subscriptions;

    @Given("^I have subscribed for a marketing service with my e-mail (.*)$")
    public void i_have_subscribed_for_a_marketing_service_with_my_e_mail_tsu_kth_se(String address) throws Throwable {
        subscriptions = new Subscriptions();
        subscriptions.subscribe(address);
    }

    @When("^I unsubscribe (.*)$")
    public void i_unsubscribe_tsu_kth_se(String address) throws Throwable {
        message = subscriptions.unsubscribe(address);
    }

    @Then("^I should get a goodbye message$")
    public void i_should_get_a_goodbye_message() throws Throwable {
        assertTrue("The unsubscribe message should contain 'Goodbye' and it was <" + message + ">", message.contains("Goodbye"));
    }

    @Then("^(.*) should be unsubscribed$")
    public void tsu_kth_se_should_be_unsubscribed(String address) throws Throwable {
        assertTrue("The unsubscribe message should contain 'Goodbye' and it was <" + message + ">", message.contains("Goodbye"));
    }

    @Then("^I should get an error message$")
    public void i_should_get_an_error_message() throws Throwable {
        assertTrue("Unsubscription failed with the message <" + message + ">", message.contains("Not unsubscribed"));
    }

    @Then("^(.*) should not be unsubscribed$")
    public void tsu_kth_se_should_not_be_unsubscribed(String address) throws Throwable {
        assertTrue(address + " should be a subscriber", subscriptions.isSubscribing(address));
    }
}

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:

package se.thinkcode.jdd;

import java.util.HashSet;
import java.util.Set;

public class Subscriptions {
    private Set<String> subscribers;

    public Subscriptions() {
        subscribers = new HashSet<String>();
    }

    public String subscribe(String address) {
        subscribers.add(address);

        return validateEmail(address);
    }

    public String unsubscribe(String address) {
        if (isSubscribing(address)) {
            subscribers.remove(address);
            return "Goodbye " + address;
        } else {
            return "Not unsubscribed " + address;
        }
    }

    public boolean isSubscribing(String address) {
        return subscribers.contains(address);
    }

    private String validateEmail(String address) {
        if (address.contains("@")) {
            return "Welcome " + address;
        } else {
            return "There was an error with the address " + address;
        }
    }
}

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.

Conclusion

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.

Resources



(less...)

Pages

About
Events
Why

Categories

Agile
Automation
BDD
Clean code
Continuous delivery
Continuous deployment
Continuous integration
Cucumber
Culture
Design
DevOps
Executable specification
Git
Gradle
Guice
J2EE
JUnit
Java
Javascript
Kubernetes
Linux
Load testing
Maven
Mockito
New developers
Pair programming
PicoContainer
Presentation
Programming
Public speaking
Quality
React
Recruiting
Requirements
Scala
Selenium
Software craftsmanship
Software development
Spring
TDD
Teaching
Technical debt
Test automation
Tools
Web
Windows
eXtreme Programming

Authors

Thomas Sundberg
Adrian Bolboaca

Archives

Meta

rss RSS