We’ve Got You Covered

Software Engineering Done Right — s01e04

Behavior Driven Development (BDD) is an important aspect of developing software the right way. It allows for defining an unambiguous set of requirements that software needs to fulfil. Among the more prevalent benefits is providing the Product Development Team a way to automate the acceptance phase and to move the acceptance of the software to the earliest stage of the development cycle. So, feedback is delivered to the engineer as soon as possible.

BDD is closely related to Test Driven Development (TDD) and follows the same pattern. Of course, having tests is great and knowing that tests are passing or failing is crucial to getting an insight in the level of quality of the product being developed. Another aspect that is important for a raised level of confidence is understanding how much of the critical success factors of the product under development is covered by any form of testing. In the previous installments of this series I laid out the groundwork for both test automation and code coverage. Now it is time to add some dressing to our Cucumber — Gherkin salad.

What happened before…

In the second episode of this series I introduced you to Behavior Driven Development (BDD) through the Cucumber framework and Gherkin, the domain specific language (DSL) for writing usage scenarios or rather defining product behavior. In case you missed that episode or want a refresher, follow the link to the article on Medium.

The third episode was all about code coverage and how to measure it using the tool JaCoCo. I included some rules of thumb on which coverage objectives are sensible and why. I highlighted that 100% coverage should not be the goal and is probably a waste of time. I also explained the value of coverage metrics to the software engineer and why, from a management perspective, coverage is typically not an indicator of software quality. In case you missed this episode or want a refresher, follow the link to the article on Medium.

Both episodes are completed with a branch in my Github repository. All branches are named with an indicator to the episode and can be found here:

Getting up to speed

Once you’ve cloned the repository you have access to the sources of this episode. Go ahead and run a gradlew build. This will give us errors because we have our scenario defined but we still need to tell Cucumber what to do when it executes the scenario. The errors we get from Gradle are helpful in this.

When you clone the repository and use the branch related to an episode, you will get all code up to that episode. You can build the project using Gradle which is included with the repository. For Windows you would run from the command-line: gradlew build

When your operating system is either MacOS or Linux, you would run from the command-line: ./gradlew build

In case you are working with the branch s01e03 your output will show that you have some code missing. This is by design and in this episode we will add the missing source code. When you run Gradle from the command-line on s01e03, the results will be as seen below.

io.cucumber.junit.platform.engine.UndefinedStepException: There were 3 undefined steps
The step "some initial state" is undefined. You can implement it using the snippet(s) below:
@Given("some initial state")
public void some_initial_state() {
// Write code here that turns the phrase above into concrete
actions
throw new io.cucumber.java.PendingException();
}
The step "this is a trigger for certain behavior" is undefined. You can implement it using the snippet(s) below:@When("this is a trigger for certain behavior")
public void this_is_a_trigger_for_certain_behavior() {
// Write code here that turns the phrase above into concrete
actions
throw new io.cucumber.java.PendingException();
}
The step "certain behavior is exposed" is undefined. You can implement it using the snippet(s) below:@Then("certain behavior is exposed")
public void certain_behavior_is_exposed() {
// Write code here that turns the phrase above into concrete
actions
throw new io.cucumber.java.PendingException();
}

As you can see, Gradle and Cucumber go together helping us doing the right thing. They emitted some boilerplate code to have executed by Cucumber when it reads the scenario. We can just copy the methods into our RunCucumberTest class and we are good to go.

Filling in the Blanks

Our first behavior to be implemented is defined by the feature defined in the file phonebook.feature.

We need to implement three aspects of the requested behavior.

  1. Give: In this scenario, the scenario is valid for those situations where the contact Arc-E-Tect is defined in the phonebook.
  2. When: The second aspect tells us that the contact has a phone number that is retrievable.
  3. Then: The third aspect tells us that when we do what was defined in the ‘when’ clause. The desired behavior is that we get a specific phone number.

From the scenario above we know that there is something called a Phonebook and there is something called a Contact, which is stored in the phone book. We also know that Contacts have a phone number. And we know that the Contact called Arc-E-Tect has the phone number “+1 555 3247843”. This is enough information for us to develop our application such that it will display the behavior as described in the given scenario.

While you will be going through the description of how to implement the requested behavior, you will notice that the code makes no assumptions at all. The implementation is very puritan and will implement the exact behavior as described in the scenario.

Given

Most of the code will be written as part of the implementation of the Given clause. I renamed the application from App to PhonebookApplication and I removed the method getGreeting() because that has nothing to do with a Phone book application and there is no need for that method based on our described features.

I am going to create a new class Contact which will have one field, the contact’s Phone number. Please note that the scenario is very clear on that there is something called a Contact in the business’ vocabulary. This is the only reason I am creating this class. This is an example of the power of BDD, it conveys the business vocabulary to the engineers. Consider the situation where the Given clause was defined as Given “Arc-E-Tect” is in our Phonebook with phone number “+1 555 3247843”, then there would not have been the notion of a Contact in our business application.

You should also note that the Contact only has a phone number, not a name. This might strike you as odd, but the scenario does not say anything about a name, so we are not making any assumptions about a name. What would typically happen is that during discussions between users, Product Owner, and software engineers, it would become evident that Contacts need to have a name as well. But that will open a whole can of worms. Names are not always what they seem and are not trivial. Names are not discussed in this episode.

Because we need to be able to ensure that there is a Contact that can be retrieved through the search string “Arc-E-Tect” with a specific phone number, I added a method to our application that allows us to make certain that there is a such a Contact.

I did this through TDD, defining a test first, and then coding the method such that the test would succeed.

The first time I ran this test, I got compilation errors which was expected, since the method was not implemented by my application. After making sure that my addContactToPhonebookTest() method was implemented correctly, I proceeded with implementing our Given clause.

When

The When clause is the trigger for the requested behavior. In many cases, the implementation of the when clause is the simplest of the three clauses. In our case it tells us that the application needs to have a means of retrieving the contact based on a search string. This is merely a matter of implementing the relevant method in our application.

I used TDD to develop the new method. I will leave the sources for you to get from the Github repository, make sure you get the s01e04 branch.

There is not much to it, but we must make sure that we remember to retrieve the phone number, so we can validate its correctness.

Did you notice that we do not retrieve the Contact from the phonebook but the phone number? The scenario as it is defined requires us to retrieve the phone number, not the Contact. This is important to note. It might have been more logical to retrieve the Contact since we stored the contact as well. Consider the case where the application is in its 6th release and now also contains the person’s gender, religion, and credit card number. Retrieving the full Contact would mean that all that other sensitive data is also retrieved, which might be unlawful. Hence, we make sure that we only retrieve the data we should retrieve, it makes our application a more lawful behaving application.

Then

The Then clause is where we validate the correctness of our application’s behavior and we validate that the retrieved phone number is the same as we expected, based on our scenario.

Quality Assured

Because all our tests are executed and our code coverage is measured every time we execute gradlew build, we can see immediately how our quality is doing.

Clapp up to 50 times, to share that you like the story.

It should be pretty good because all our tests are passing. And indeed, when looking at the code coverage metric we see that this is 97%. Even though we only applied TDD on the non-trivial methods, we see that by applying BDD, and only implemented what was requested behavior, our code is covered for almost 100% by our tests. The only part that is not touched by our tests is the empty main() method of our application. This is the benefit of having spent the time on making sure that our build environment is reliable and consistent.

As always, the full source code of this episode of my Software Engineering Done Right series can be found on Github.

On the go and done reading? Check out this continuing piece of pure fiction.

@Arc-E-Tect

Disclaimer

The text very explicitly communicates my own personal views, experiences and practices. Any similarities with the views, experiences and practices of any of my previous or current clients, customers or employers are strictly coincidental. This post is therefore my own, and I am the sole author of it and am the sole copyright holder of it.

Credits

Special thanks to my lovely wife Olcay, as well as my friend Sytse who took the time and made the effort to review my article. I am confident that the article’s quality was significantly improved by their feedback.

Image credits: https://bit.ly/Old_Oak_Common_Locomotive_Depot_King_Henry_VIII

Some people see things and ask ‘Why?’ I dream of things and ask ‘Why not?’

Get the Medium app