A number of people, myself included, have a hard time getting started with test driven development (TDD). The issue lies in that it’s difficult to wrap your head around something that is rather non specific in its implementation and it is usually described as “writing your tests before you write your code”.
When developers hear “Write your tests before you write your code”, then they look at their previous test suite they wonder… “How can I possibly write this without having written the code first?
new Test.Case({
testSomething : function () {
var class = new MyClass();
class.assert(...);
},
testSomethingElse : function () {
//...
}
});
Herein Lies the difficulty with doing test driven development, everyone says you write the tests first but in reality you write the structure of the class first. To illustrate this lets create a simple instantiable class using TDD. This class will need to receive data and then append a representation of that data to the DOM.
Your first step is to outline each step the class needs to make in point form. This helps you visualize the whole completed class from start to finish before you type a single line of code. This will help you figure out any shortcomings in complex classes ahead of time so you don’t waste time writing code which will be thrown away.
Our classes requirements:
is instantiable.
will accept a config object and create related instance properties.
will generate a string template using the configuration properties.
will generate a DOM element from the string template.
will append that element to the body.
Next is to take these steps and create tests which prove that they work. To do that we need to use a test suite which makes test driven development easy. For this tutorial I am going to use Mocha (http://visionmedia.github.io/mocha/) I won’t go into detail on this test framework as it’s out of the scope of this tutorial but it is pretty easy to pick up by following along.
describe(‘DOM Info Generator’, function(){
it(‘is instantiable’);
it(‘will accept a config object and create related instance properties’);
it(‘will generate a string template using the configuration properties’);
it(‘will generate a DOM element from the string template’);
it(‘will append that element to the body’);
});
So now you can start to see what I mean when I said that we write the structure of the class first. In practice we aren’t writing the tests first at all, we are writing declarations of what our class will be doing.
The third step is to start writing some code to satisfy these tests in order. Our first two tests say that our class needs to be instantiable and that it needs to accept a configuration object and create related instance properties.
function DomInfo(config) {
this.name = config.name;
this.job = config.job
}
Now that we have the code completed to what we believe matches our first two tests we can write the tests. On a real project you would need to import your class into your test suite but for this tutorial we will just have our test suite after our class declaration.
var assert = require(‘assert’);
describe(‘DOM Info Generator’, function() {
var name = 'Spock',
job = 'Science Officer',
domInfo;
// Code to execute before every test.
beforeEach(function() {
domInfo = new DomInfo({
name: name,
job: job
});
});
// Code to execute after every test.
afterEach(function() {
domInfo = null;
});
// Tests
it(‘is instantiable’, function() {
assert.equal(domInfo instanceof DomInfo, true);
});
it(‘will accept a config object and create related instance properties’,
function() {
assert.equal(domInfo.name, name);
assert.equal(domInfo.job, job);
});
...
});
Stretching a little bit out of context of this tutorial I am setting up hooks (beforeEach, afterEach) to create a new instance of our class for every test, and then clean up that instance after every test.
For our first test we take our new instance and test to make sure that it is an instance of our constructor. The second test we access the two instance properties of our class and check them against our known values.
Moving on to our next two tests we need to take our configuration values generate a template and generate a DOM element from that template.
function DomInfo(config) {
this.name = config.name;
this.job = config.job
this.generateAndAppendTemplate();
}
DomInfo.prototype = {
generateAndAppendTemplate: function() {
this.generateTemplate();
this.createDOMElement();
this.appendElement();
},
generateTemplate: function() {
this.template = ‘Hi ’ + this.name + ‘! Great job being a ‘ + this.job;
},
createDOMElement: function() {
var greeting = document.createElement('div');
greeting.className = 'greeting';
greeting.textContent = this.template;
this.element = greeting;
},
appendElement: function() {
document.body.appendChild(this.element);
}
}
And now to the tests to make sure that our code satisfies our goals. I left out the previous code and tests for brevity.
...
it(‘will generate a string template using the config properties’,
function() {
assert.equal(typeof domInfo.template === ‘string’);
assert.notEqual(domInfo.template.indexOf(name’), -1);
assert.notEqual(domInfo.template.indexOf(job), -1);
});
it(‘will generate a DOM element from the string template’, function() {
assert.equal(typeof domInfo.element === ‘object’);
});
it(‘will append that element to the body’, function() {
assert.notEqual(document.querySelector(‘.greeting’), null);
});
By now you are probably getting the hang of this.
To recap, the easiest way to understand test driven development, or TDD, is to outline our class into declarations of intent. Then, test by test, write the code to satisfy those declarations and the tests to ensure that they stay satisfied. Running the tests constantly to be sure that we are always moving forward. If you find that after satisfying one of your new tests, or after a refactor, you have broken some tests you must then stop and make those tests pass again. You cannot advance unless all previous tests pass.
I hope that this approach will help those of you who haven’t been able to wrap your head around test driven development be productive. Properly developing large applications using test driven development helps to all but remove code rewrites from improperly spec’d code and hidden regressions because you outline the classes intent and knock off those points one by one.
Thanks for reading, and as always, let me know what you think by commenting below or mentioning me on twitter @fromanegg.