Introduction to TDD/BDD: How to Get Started in Any Language

Test-driven development (TDD) and behaviour-driven development (BDD) are two approaches to software development that can help you write cleaner, more robust and less error-prone code. In this tutorial, we’ll be discussing both approaches and walking through a simple example in JavaScript. While these skills are not essential to building software, they can save you time and effort in the long run.

The average salary in the UK (excluding London) for developers with knowledge of TDD was £47,500 in the six months to August 2018 – a huge 5.56% increase on the same period in 2017. Knowledge of TDD and BDD is sought after in the software development world; mastering them may prove advantageous in the hunt for your next job.

Test-Driven Development

Imagine you’ve been given a new assignment at work. You’ve been asked to create and integrate a new function which computes the floor area for a room based on user inputs. “Simple!” you remark and get to work writing some of the best code of your career. When you integrate the code into your existing project and hit run, the code compiles; and out pops a beautiful number representing the floor area of your specified room. Job done. It’s not until weeks later that a customer contacts you to relay that the carpet they purchased doesn’t fit their bedroom. Sound familiar?

Ok, so perhaps a simple piece of functionality is hard to mess up – but as your code base becomes larger, it can be easy to make mistakes. So how do we ensure this doesn’t happen? This is where test-driven development comes into play.

To dive into test-driven development, we need to first understand the concept of a unit test. This is simply a process in which small parts of code, or units, are tested individually. Once a unit is tested and the proper function is confirmed, we can then refactor the existing code while ensuring that it still passes the test.

Test-driven development is simply the procedure of writing the test before the code that fulfils the test criteria, and it follows five basic steps:

  1. Write a test for a particular piece of functionality
  2. Run the test and check that it fails
  3. Write the code to fulfill the test criteria
  4. Run the test and check that it passes
  5. Refactor your code while maintaining passing the test

Let’s put this into practice. Remember that floor area code our customer was complaining about earlier? We’ll use test-driven development to create an efficient piece of code that we’re positive produces the correct results. In this walk-through, we’ll be using JavaScript and the mocha testing framework to produce our unit tests.

Now, let’s create a simple test to describe the code we want to write. We’ll be writing our code in a file called calculations.js while we’ll be writing our test in test.js. We’ll use mocha’s describe() to group the tests we want to run.

First, we’ll describe() a set of tests, which we’ll use to group together all of the tests from our calculations.js file. Let’s call this ‘calculationTests’.

Next, we describe() getFloorArea so that we can group all of the tests we want to associate with the getFloorArea function we’ll eventually write in our calculations.js file.

Finally, let’s write the test itself using mocha’s it(). We’ll define a short message to describe the test and assert that the output from our function is the value that we expect.

//test.js

var assert = require('assert')
var calculations = require('./calculations.js')

describe('calculationTests', function() {
describe('#getFloorArea(length, width)', function() {
it('should return floor area based on width and length', function(){
assert.equal(calculations.test.getFloorArea(10, 20), 200)
});
})
})

Let’s run the test using node.js. To do this, we simply run npm run test in the command-line.

 

Don’t worry, this is all according to plan. Because our test fails we can now begin to write the code to fulfil our test criteria.

Firstly, let’s create our calculations.js file and define a getFloorArea function. We’ll specify two input parameters – a room length, and a room height.

Below the new function, we’ll define exports.test so that our unit test can access the newly created code.

//calculations.js

function getFloorArea(length, width){
return length * width;
}

exports.test = {
getFloorArea: getFloorArea
}

When we hit run a second time, we can see that our test passes:

That’s all there is to it. We’ve successfully developed a piece of code using test-driven development! If our code was more complex, we could now move on to step five and refactor our code while ensuring that our test criteria are still met.

Most programming languages have frameworks that enable unit testing. For example in Java, JUnit is the go-to framework for writing unit tests and developing software in a test-driven manner. For C#, NUnit is one of the most popular frameworks while JavaScript has numerous frameworks including Mocha, Jest and Jasmine.

Introduction to Behaviour-Driven Development

Behaviour-driven development has evolved from the principles of TDD. In its simplest form, BDD can be described as a set of principles to help write more logical, self-documenting and behaviour focused tests.

Most commonly, BDD follows a ‘given’, ‘when’, ‘then’ pattern, in which each step denotes a piece of behaviour that we can imitate using code. Taking our earlier example, we can split this into the three steps:

  1. Given a user wants to compute a floor area
  2. When the floor area is computed
  3. Then check the floor area is computed correctly

Let’s see how this might look in our code example:

var assert = require('assert')
var calculations = require('./calculations.js')

describe('calculateTests', function() {
describe('#getFloorArea(length, width)', function() {
it('should return floor area based on width and length', function(){
//given a user wants to computer a floor area
var length = 10;
var width = 20;

//when floor area computed
var floorArea = calculations.test.getFloorArea(length, width);

//then floor area is computed correctly
assert.equal(floorArea, 200)
});
})
})


By sticking to the BDD language, programmers who have to use or maintain your code can get a sense of what your code does, and how it is meant to be used. Remember – that could be you six months after writing it!Although the test is larger, it shows a clear path through the logic. It becomes meaningful for anyone viewing it, and represents much more than a humble unit test; each test tells a story which can embody the behaviour of a potential user.

Conclusion

By developing code in a test-driven way, we can write software that’s less prone to errors. By writing the unit test before the code that fulfils it, we can be certain that it works in the way that we intend.

When we extend the principles of test-driven development by adopting behaviour-driven development, we create tests that do more than just validate the functionality of our code. We begin to build anecdotes that describe the behaviours of our potential user. Not only does this help the developer to understand the user’s journey, it also creates self-documenting and descriptive tests.

If you're looking for your next job as a software engineer, have companies apply to you by adding your profile to Snap.hr.