Test Driven Development — Breaking Down Unit & Integration Tests

Rahul Mody
Apr 28, 2017 · 5 min read

This blog was written by Rahul Mody via Waffle’s brand new Just The Toppings program. Interested in contributing? Email us at megan@waffle.io

What is Test Driven Development?

Testing is an essential part of the software development cycle. Writing tests is great for debugging, but it can also provide an unrealized benefit of providing a process flow for writing code. By reading and writing tests, developers have clarity on what they are building. This type of design process, using tests to guide you through writing code, is formally known as Test Driven Development.

Test Driven Development (TDD) was introduced/rediscovered in 2003 by Kent Beck, the creator of extreme programming (a software development methodology) and one of the 17 signatories of the Agile Manifesto. TDD is, simply put, a software design process in which writing tests comes before writing code.

The general thought process of TDD is:

  1. Write a test that fails
  2. Write code to pass the test
  3. Refactor the code to make it right
http://agiledata.org/essays/tdd.html#WhatIsTDD

A common mantra in the TDD community that summarizes TDD is “Red, Green, Refactor”

  1. Red: write a test that doesn’t work
  2. Green: pass the test with code that works, but is not necessarily clean
  3. Refactor: clean the code

TDD differs dramatically from something like architecture-driven design, where clean code is written first and then other parts of the “unit” are integrated after to solve the “does it work” problem.

While the concept of TDD may be simple to understand, the implementation can be more difficult. In fact, implementing any testing style can be difficult simply due the variety of testing terminology that exists today and the misconceptions that arise between each type of testing. Take a look at http://www.aptest.com/testtypes.html for example. There a 14 different types of tests described, of which, some are scoped inside of another, adding additional layers of complexity. The best way to understand each of these different types of tests is to realize that some of these testing types are written from the perspective of a developer, while others are written from the perspective of a customer/end user. In this post, we will be focused on testing from a developer’s perspective.

From a developer’s perspective, TDD can get convoluted when thinking about how to determine each “unit” to test and how to write tests that cover the “integration” of these smaller units. TDD requires at a minimum, two styles of tests to successfully cover an application, Unit Tests and Integration Tests.

Unit Tests

At the smallest level, tests need to be written that cover a specific function in a program. These are commonly referred to as Unit Tests. A Unit test should:

  • be quick to run
  • be independent from other tests
  • have the minimum number of assertions
  • avoid pre-conditions as much as possible
  • check the results of the function, not the inner workings
  • uses clear descriptions to define what the test is doing

When writing unit tests, it is best to go in order of answering the following questions:

  1. What are you testing?
  2. What should it do?
  3. What is the expected output?
  4. Does your actual output meet the expected output?
  5. Do you need any pre-conditional information to run the test?

It may be confusing to think about any pre-conditional information in the last step, but when thinking about writing the test, it is easier to start with what your are expecting and then add in the pre-conditional information after, based on what is required to compare the expected vs actual output.

Let say we want to write a unit test for checkPassword(). This example is written in Mocha, a testing suite for Javascript:

describe(‘checkPassword’, () => {
it(‘should return false if the password is less than 6 digits’,
() => {
const result = checkPassword(‘ab123’);
expect(result).to.be.false
});
it(‘should return false if the password contains any script tag’,
() => {
const result = checkPassword(‘<s>hack<s>’);
expect(result).to.be.false;
});
});

This unit test is asserting that a password is not less than 6 digits and that it does not contain any “<” or “>” characters. It only checks one function and does not go into the intricacies of how the function works, but rather that it simply works. In TDD, you would start by writing this test and immediately fail this test because this function do not exist. Your next step would be to write a function that makes sure the checkPassword() exists and works. You can then refactor the code to simply and remove any duplication in code.

Integration Testing

The next level up from unit testing is Integration Testing. Integration Testing looks to see if each of these unit tests work together to properly perform a component of the application. When running integration tests, the assumption is that the unit tests for each piece of a component being tested are already working. In an large application, integration testing can be expanded out to multiple levels, meaning that integration tests can be written to test integration of units that make up a component, as well as test the integration between these components. The point of an integration test in TDD is to guide the developer to connecting independent units, whether they are components or individual functions.

Let’s take the original unit test a step further and create an integration test for a Sign Up Page. You will notice that the Integration Test can typically involve routes, views, and models accessing the database, i.e. commonly testing controllers in the MVC framework. It is common that many integration tests will include stubs and mocks, however the example below does not. The example below still uses Mocha, but this time includes the Chai Assertion Library with the jQuery library.

var request = require(‘request’);
var assert = require(‘assert’);
var app = require(‘../app.js’);
var base_url = ‘http://localhost:3000/signup';
describe(‘sign up page’, function() {
describe(“GET /signup”, function() {
it(“returns status code 200”, function(done) {
request.get(base_url, function(error, response, body) {
assert.equal(200, response.statusCode);
done();
});
});
it(“returns signup form”, function(done) {
request.get(base_url, function(error, response, body) {
expect($(‘body’)).to.have.html($(‘form.signup’));
app.closeServer();
done();
});
});
});
describe(‘POST /signup’, function() {
it(“add as user to the database if password is valid”,
function(done) {
//asserts that session id is set when User.signup is
called
});
});
it(“redirects to login page”,
function(done) {
//asserts that body has login form
});
});
...

This integration test is testing the routes as well as the methods that are required to perform the actions of signing up. Notice that we are not checking if specific methods work, but rather they are working together.

In summary TDD requires both concise unit tests and integration tests. Without Integration Tests, you may have individual units working correctly but together performing poorly:

In order to make this blog happen I relied on some great resources on the web:

I also want to mention that there is a lot of information about writing test scripts, and many people have differing opinions about how it is done and the terminology used. It seems that alot sources originate from Test Driven Development, an Example by Kent Beck, which may be the holy grail of TDD.

Rahul Mody

Written by

Full Stack Web Developer, GA Tech Industrial Engineer, lover of breakfast food, working to make the world a better place

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade