Testing With Platoon

ATTEN-SHUN! Privates, what we have here is a failure to communicate! Just because you're writing applications in the new shiny tech DOES NOT mean you have an excuse not to TEST! As your commanding officer, I am going to whip you back into shape with Platoon!

What? Soldier, what do you mean you do not know what Platoon is?! Drop and give me 20 while I educate you! Platoon is small & flexible unit-testing library that makes the built-in assert library look like a popgun!

Welcome To Boot Camp (Installation)

Installation is easy & fairly standard. If you use npm, you can simply:

$ npm install platoon

Which will get you v0.0.3 at the time of writing. If you prefer a Git checkout, symlinking the package into your ~/.node_libraries should work just as well.

With that, grab your gear and let's get down to it!

Bringing Out The Big Guns (Getting Started)

If you pansies are going to live long enough to make it to my rank, you'd better get it straight that the only good gun is one mounted on a TANK:

var Tank = function(initial_x, initial_y, shells_remaining) {
  this.x_location = initial_x || 0;
  this.y_location = initial_y || 0;
  this.shells = shells_remaining || 10;
};

Tank.prototype.shoot = function() {
  if(this.shells >= 0) {
    console.log('Ka-Boom!');
    this.shells -= 1;
    return true;
  }
  else {
    console.log('Out of ammo!');
    return false;
  }
};

Tank.prototype.move = function(x, y) {
  // Do a distance calculation, maybe some setTimeout then...
  this.x_location = x;
  this.y_location = y;
};

exports.Tank = Tank;

An untested tank is a fast way to end up dead! So let's prove our tank's battle-worthiness. Create a tests directory alongside your code (which will help us out later). Inside that directory, drop a test_tank.js file and inside it, we'll start with the most basic check: Can we have a tank initialized properly? Our code looks like:

var platoon = require('platoon');
var Tank = require('../tank_buggy').Tank;

exports.TestTank = platoon.unit({},
  function(assert) {
    "Make sure we can have a tank of our very own.";
    var tank_1 = new Tank();
    assert.equal(tank_1.x_location, 0);
    assert.equal(tank_1.y_location, 0);
    assert.equal(tank_1.shells, 10);

    var tank_2 = new Tank(10, 20, 8);
    assert.equal(tank_2.x_location, 10);
    assert.equal(tank_2.y_location, 20);
    assert.equal(tank_2.shells, 8);
  }
);

There's a lot going on here, so we'll break it down. First, we require platoon just as we would any other module. We then add our test case to the exports, which we're inventively calling TestTank. We unleash a platoon.unit on it, which take some configuration options as it's first parameter. All other parameters are the individual tests to run. In this case, we're only testing initialization of our Tanks.

Each test takes an assert parameter, which we can use to override/extend the object performing the assertion. Though we won't cover it here, you could customize it to automate parts of your testing (inspect a certain combination of instance variables or repeat a set of actions). This is NOT the assert object that comes as part of Node, though it's basic API is virtually identical.

You'll also note that each test takes an optional string as the first line of the function. You can use this string as a description about what the test does, as we'll see shortly.

Finally, you perform the assertions & logic you need to make sure things are working properly. Now, weapons at the ready...aim...

FIRE! (Running the Tests)

For the moment, to run these tests, you can do the following:

$ cd tests
$ platoon test_tank_1.js

If everything is in order, you should get:

TestTank
    (6/6) Make sure we can have a tank of our very own.

Here's the first place where Platoon shines. This output tells you which test cases it ran (in this case, just TestTank), lists out the ratio of tests that passed and outputs the description of which portion of the case just ran. For an added bonus, the output is properly color-coded, so we're all green.

Let's add some more tests to make sure we don't have any other bugs:

var platoon = require('platoon');
var Tank = require('../tank_buggy').Tank;

exports.TestTank = platoon.unit({},
  function(assert) {
    "Make sure we can have a tank of our very own.";
    var tank_1 = new Tank();
    assert.equal(tank_1.x_location, 0);
    assert.equal(tank_1.y_location, 0);
    assert.equal(tank_1.shells, 10);

    var tank_2 = new Tank(10, 20, 8);
    assert.equal(tank_2.x_location, 10);
    assert.equal(tank_2.y_location, 20);
    assert.equal(tank_2.shells, 8);
  },
  function(assert) {
    "Check that movement is working properly.";
    var tank_1 = new Tank();
    assert.equal(tank_1.x_location, 0);
    assert.equal(tank_1.y_location, 0);

    tank_1.move(5, 6);
    assert.equal(tank_1.x_location, 5);
    assert.equal(tank_1.y_location, 6);
  },
  function(assert) {
    "Shoot the cannon!";
    var tank_1 = new Tank(0, 0, 2);
    assert.equal(tank_1.shells, 2);

    assert.equal(tank_1.shoot(), true);
    assert.equal(tank_1.shells, 1);

    assert.equal(tank_1.shoot(), true);
    assert.equal(tank_1.shells, 0);

    tank_1.shoot();
    assert.equal(tank_1.shells, 0);
  }
);

If we run our tests now with platoon test_tank_2.js, you'll see we've exposed a bug in our code. You should get output like:

Ka-Boom!
Ka-Boom!
Ka-Boom!
TestTank
    (6/6) Make sure we can have a tank of our very own.
    (4/4) Check that movement is working properly.
    (5/6) Shoot the cannon!
            -1 should == 0
            at Object.<anonymous> (/Users/daniel/Desktop/howtonode.org/articles/testing-with-platoon/tests/test_tank_2.js:39:12)
        ... (full traceback here)

You can see we've made a mistake in our Tank.shoot method by checking this.shells >= 0 instead of just this.shells > 0. You'll also note that unlike Node's assert, any console.log statements are NOT suppressed, which can be a boon (or curse) when debugging.

A simple fix to our Tank code like so:

var Tank = function(initial_x, initial_y, shells_remaining) {
  this.x_location = initial_x || 0;
  this.y_location = initial_y || 0;
  this.shells = shells_remaining || 10;
};

Tank.prototype.shoot = function() {
  if(this.shells > 0) {
    console.log('Ka-Boom!');
    this.shells -= 1;
    return true;
  }
  else {
    console.log('Out of ammo!');
    return false;
  }
};

Tank.prototype.move = function(x, y) {
  // Do a distance calculation, maybe some setTimeout then...
  this.x_location = x;
  this.y_location = y;
};

exports.Tank = Tank;

And running our tests yields:

Ka-Boom!
Ka-Boom!
Out of ammo!
TestTank
    (6/6) Make sure we can have a tank of our very own.
    (4/4) Check that movement is working properly.
    (6/6) Shoot the cannon!

Good job, soldier! We'll make a man (or woman) out of you yet!

Communicating With HQ (Async Tests)

Better output & flexibility aren't the only reasons to use Platoon though. Platoon also makes it dead simple to test asynchronous operations. Simply wrap asynchronous calls in assert.async and test the callback as normal. For example, we've got a Grenader that looks like:

var Grenader = function (timer) {
  this.timer = timer || 5;
};

Grenader.prototype.throw = function(callback) {
  console.log("Fire in the hole!");
  setTimeout(function() {
    callback('Ka-Boom!');
  }, this.timer * 1000);
};

exports.Grenader = Grenader;

To test Grenader.throw, we write tests like:

var platoon = require('platoon');
var Grenader = require('../grenader').Grenader;

exports.TestGrenader = platoon.unit({},
  function(assert) {
    "Throw the grenade and make sure it goes ka-boom.";
    var grenader_1 = new Grenader();
    grenader_1.throw(assert.async(function(result) {
      assert.equal(result, 'Ka-Boom!');
    }));
  }
);

Our output (after 5 seconds) looks like:

Fire in the hole!
TestGrenader
    (1/1) Throw the grenade and make sure it goes ka-boom.

Easy and ensures our callbacks are working correctly! If we keep up like this, the war will win itself!

Fall In! (Multiple Tests & Building A Suite)

Last but not least, running each test by itself is as error-prone as handing a new recruit a loaded assault rifle, and almost as messy. Fortunately, building a suite is trivial. Inside our tests directory, drop in a tests.js that looks like:

var tank = require('./test_tank_3.js');
var grenader = require('./test_grenader.js');

exports.TestTank = tank.TestTank;
exports.TestGrenader = grenader.TestGrenader;

Now running the full suite is as simple as:

$ platoon

You can run it like this from either within the tests directory as we have been, or up a directory alongside your application code. Running the suite this way generates the following output:

Ka-Boom!
Ka-Boom!
Out of ammo!
Fire in the hole!
TestTank
    (6/6) Make sure we can have a tank of our very own.
    (4/4) Check that movement is working properly.
    (6/6) Shoot the cannon!
TestGrenader
    (1/1) Throw the grenade and make sure it goes ka-boom.

Join The Cadence (Conclusion)

I don't know but I've been told, Testing with Platoon makes me bold! Red and green, refactor too, Runs my whole suite tried and true!

All my tests, they end up green, See them in my terminal screen! Testing async is a gem, Never be without tests again!

Platoon is a fairly young project and there's plenty of room for features and improvement, but I find it's better for my workflow than the vanilla assert and hopefully you're inspired to give it a try. You who are about to test, we salute you!

Disclaimer - I have no connections to the military, everything here was blatant stereotype, I don't endorse war machines of any type, please don't flame me, yada yada yada. If you didn't find the tone/wording humorous, sorry. Please mail a self-addressed envelope for a full refund. Offer void in Wyoming, Montana & Greenland.

Posted on September 28, 2010 @ 12:00 a.m. by Daniel