Quick Guide to mocha.js Test Driven Development (TDD) in node.js
Note: This tutorial is an intro to Testing with Mocha. If you are looking for a more detailed Test Driven Development (TDD) Tutorial see: https://github.com/nelsonic/learn-tdd
We all know Cowboy Coders. (If you don't, it's you!)
The "I just get things done" developer who writes "quick fixes" and maintains "I don't have time to write tests" or "Writing tests for my code takes longer" and then acts surprised when everything starts breaking ... "it was working this morning" ...
npm install mocha -g --save-dev
You should see some output confirming it installed:
More info: http://mochajs.org/#installation
Tip: avoid installing node.js modules using
sudo
see: http://stackoverflow.com/questions/16151018/npm-throws-error-without-sudo
In your project create a new /test directory to hold your tests:
mkdir test
Now create a new file ./test/test.js in your text editor
and write/paste the following code:
var assert = require("assert"); // node.js core module
describe('Array', function(){
describe('#indexOf()', function(){
it('should return -1 when the value is not present', function(){
assert.equal(-1, [1,2,3].indexOf(4)); // 4 is not present in this array so indexOf returns -1
})
})
});
By typing the command mocha in your terminal the mocha comand line program will look for a /test directory and run any .js files it contains:
mocha
While I'm the first to agree that cash-less payments are the future, paying with cash is something everyone can relate to and is therefore a good example to use. (think of better TDD example? tell me!)
Given a Total Payable and Cash From Customer Return: Change To Customer (notes and coins).
Essentially we are building a simple calculator that only does subtraction (Cash - Total = Change), but also splits the result into the various notes & coins.
In the UK we have the following Notes & Coins:
see: http://en.wikipedia.org/wiki/Banknotes_of_the_pound_sterling (technically there are also £100 and even £100,000,000 notes, but these aren't common so we can leave them out. ;-)
If we use the penny as the unit (i.e. 100 pennies in a pound) the notes and coins can be represented as:
- 5000 (£50)
- 2000 (£20)
- 1000 (£10)
- 500 (£5)
- 200 (£2)
- 100 (£1)
- 50 (50p)
- 20 (20p)
- 10 (10p)
- 5 (5p)
- 2 (2p)
- 1 (1p)
this can be represented as an Array:
var coins = [5000, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 2, 1]
Note: the same can be done for any other cash system ($ ¥ €) simply use the cent, sen or rin as the unit and scale up notes.
If you are totally new to TDD I recommend reading this intro article by Scott Ambler (especially the diagrams) otherwise this (test-fail-code-pass) process may seem strange ...
In Test First Development (TFD) we write a test first and then write the code that makes the test pass.
so, back in our ./test/test.js file add the following line:
var C = require('../cash.js'); // our module
Back in your terminal window, re-run the mocha command and watch it fail:
mocha
This error ("Cannot find module '../cash.js'") is pretty self explanatory. We haven't created the file yet so test.js is requesting a non-existent file!
Q: Why deliberately write a test we know is going to fail...?
A: To get used to the idea of only writing the code required to pass the current (failing) test.
Create a new file for our cash register cash.js:
touch cash.js
Note: We are not going to add any code to it just yet.
Re-run the mocha command in terminal, it will pass (zero tests)
Lets add a test to ./test/test.js and watch it fail again:
var assert = require("assert"); // core module
var C = require('../cash.js'); // our module
describe('Cash Register', function(){
describe('Module C', function(){
it('should have a getChange Method', function(){
assert.equal(typeof C, 'object');
assert.equal(typeof C.getChange, 'function');
})
})
});
Re-run mocha
:
Add the following to cash.js:
var C = {}; // C Object simplifies exporting the module
C.getChange = function () { // enough to satisfy the test
'use strict';
return true; // also passes JSLint
};
module.exports = C; // export the module with a single method
Re-run mocha
(now it passes):
Going back to the requirements, we need our getChange method to accept two arguments/parameters (totalPayable and cashPaid) and return an array containing the coins equal to the difference:
e.g:
totalPayable = 210 // £2.10
cashPaid = 300 // £3.00
difference = 90 // 90p
change = [50,20,20] // 50p, 20p, 20p
Add the following test to ./test/test.js:
it('getChange(210,300) should equal [50,20,20]', function(){
assert.deepEqual(C.getChange(210,300), [50,20,20]);
})
Note: use assert.deepEqual for arrays see: http://stackoverflow.com/questions/13225274/
What if I cheat?
C.getChange = function (totalPayable, cashPaid) {
'use strict';
return [50, 20, 20]; // just enough to pass :-)
};
This will pass:
This only works once. When the Spec (Test) Writer writes the next test, the method will need to be re-written to satisfy it.
Let's try it. Work out what you expect:
totalPayable = 486 // £4.86
cashPaid = 1000 // £10.00
difference = 514 // £5.14
change = [500,10,2,2] // £5, 10p, 2p, 2p
Add the following test to ./test/test.js and re-run mocha
:
it('getChange(486,1000) should equal [500, 10, 2, 2]', function(){
assert.deepEqual(C.getChange(486,1000), [500, 10, 2, 2]);
})
As expected, our lazy method fails:
We could keep cheating by writing a series of if statements:
C.getChange = function (totalPayable, cashPaid) {
'use strict';
if(totalPayable == 486 && cashPaid == 1000)
return [500, 10, 2, 2];
else if(totalPayable == 210 && cashPaid == 300)
return [50, 20, 20];
};
The Arthur Andersen Approach gets results:
But it's arguably more work than simply solving the problem. Let's do that instead. (Note: this is the readable version of the solution! feel free to suggest a more compact algorithm)
var C = {}; // C Object simplifies exporting the module
C.coins = [5000, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 2, 1]
C.getChange = function (totalPayable, cashPaid) {
'use strict';
var change = [];
var length = C.coins.length;
var remaining = cashPaid - totalPayable; // we reduce this below
for (var i = 0; i < length; i++) { // loop through array of notes & coins:
var coin = C.coins[i];
if(remaining/coin >= 1) { // check coin fits into the remaining amount
var times = Math.floor(remaining/coin); // no partial coins
for(var j = 0; j < times; j++) { // add coin to change x times
change.push(coin);
remaining = remaining - coin; // subtract coin from remaining
}
}
}
return change;
};
Add one more test to ensure we are fully exercising our method:
totalPayable = 1487 // £14.87 (fourteen pounds and eighty-seven pence)
cashPaid = 10000 // £100.00 (one hundred pounds)
difference = 8513 // £85.13
change = [5000, 2000, 1000, 500, 10, 2, 1 ] // £50, £20, £10, £5, 10p, 2p, 1p
it('getChange(1487,10000) should equal [5000, 2000, 1000, 500, 10, 2, 1 ]', function(){
assert.deepEqual(C.getChange(1487,10000), [5000, 2000, 1000, 500, 10, 2, 1 ]);
});
We are using istanbul for code coverage. If you are new to istanbul check out my brief tutorial: https://github.com/nelsonic/learn-istanbul
Install istanbul:
npm install istanbul -g
Run the following command to get a coverage report:
istanbul cover _mocha -- -R spec
You should see:
or if you prefer the lcov-report:
100% Coverage for Statements, Branches, Functions and Lines.
If you are new to Travis CI check out my tutorial: https://github.com/nelsonic/learn-travis
Visit: https://travis-ci.org/profile Enable Travis for learn-travis project
Mocha is a JavaScript test framework running on node.js and the browser.
Made by TJ Holowaychuk creator of Express (by far the most popular node.js web framework), Mocha is TJ's answer to the problem of testing JavaScript.
At last count there were 83 testing frameworks listed on the node.js modules page: https://github.com/joyent/node/wiki/modules#wiki-testing this is both a problem (too much choice can be overwhelming) and good thing (diversity means new ideas and innovative solutions can flourish).
There's no hard+fast rule for "which testing framework is the best one?"
Over the past 3 years I've tried: Assert (Core Module), Cucumber, Expresso, Jasmine, Mocha, Nodeunit, Should, and Vows
My criteria for chosing a testing framework:
- Simplicity (one of TJ's stated aims)
- Elegance (especially when written in CoffeeScript)
- Speed (Mocha is Fast. 300+ tests run in under a second)
- Documentation (plenty of real-world examples: http://mochajs.org)
- Maturity (Battle-tested by thousands of developers!)
Advanced:
- Easy to Trouble-shoot (Plenty of Answered Questions on stackoverflow)
- Automatic Test Running when File Changes (using Watchr/Grunt)
- Detailed reports of test execution (extensible reports!)
- Azat's Mocha Tutorial: http://webapplog.com/test-driven-development-in-node-js-with-mocha/
- NetTuts: http://net.tutsplus.com/tutorials/javascript-ajax/better-coffeescript-testing-with-mocha/
- Grunt.js Mocha Plugins: http://gruntjs.com/plugins/mocha
- Test Coverage with Mocha: http://stackoverflow.com/questions/16633246/code-coverage-with-mocha
- Wikipedia (duh!): http://en.wikipedia.org/wiki/Test-driven_development
- Excellent Explanation by Scott Ambler: http://www.agiledata.org/essays/tdd.html
- Testing takes "twice as long" (Myth): http://googletesting.blogspot.co.uk/2009/10/cost-of-testing.html
- Estimating Testing Effort as % of Development Time: http://stackoverflow.com/questions/1595346/estimating-of-testing-effort-as-a-percentage-of-development-time
- Technical Debt (Bad Code): http://jessewarden.com/2010/07/agile-chronicles-12-technical-debt.html
- Agile = an excuse for cowboys? Discussion: http://programmers.stackexchange.com/questions/11188/is-the-agile-approach-too-much-of-a-convenient-excuse-for-cowboys
- TDD Examples: http://stackoverflow.com/questions/1920259/recommend-good-online-sample-walkthrough-of-tdd/7213630#7213630
- Sudoku: http://johannesbrodwall.com/2010/04/06/why-tdd-makes-a-lot-of-sense-for-sudoko/
- Vending machine.
- Cash Register.
- Roman Numerals: http://www.diveintopython.net/
Code without tests is like a building without a foundation!
It's only a matter of time before it all comes crashing down ...
Is Test Driven Development (TDD) a silver bullet for all my software development woes? Short answer: No. There is a lot more that goes into writing great software than just having tests. But without tests reliability is impossible.
If you are not doing TDD in your projects I'm probably not going to be the one to change your mind by evangelizing about it. I know plenty of people calling themesleves "developers" who stubbornly cling to the idea that testing is for "QA" or "That's why we have testers" and wish them nothing but the best of luck! I just can't work with you or use your "product", no hard feelings. :-)