Buckle Up with Tape…

Marco Romero
7 min readAug 27, 2015

--

Everybody knows how important it is to fasten your seat belt when you are about to drive your car. Or how vital is to secure your harness and ropes before you start rock climbing. I mean, you can still do any of those things without the proper security measures, but chances are things could go pretty bad if you make a non calculated move.

Software development can also become a high risk sport. One plagued with sleepless deployment nights, hours of staring to cryptic error messages, swarms of bugs that eat the crops of your application, and people picking on you for not being able to do magic with your computer.

thank you Murphy…

It doesn’t have to be this way. Developing software applications should be an enjoyable ride full of nurturing experiences, and not a walk in Silent Hill school’s building…after the siren’s sound. Do yourself and your team a favor and buckle up your code!!

Now you would be asking “How do I buckle that?” Well, test first.

Test Driven Development and Unit Testing

I do not intend to do a full explanation of TDD and Unit Testing since there are plenty of articles and books about those topics that, for sure, can explain them better than I could.

What I do want, is to show you how TDD works as your code seat belts and air bags, making your software application ride it’s own evolution path safely.

Try to think in software like you would do for any living organism. It grows, changes, spreads , and dies when is left unsupported. The same rules of evolution apply to it:

“Is not the most performant, nor the most popular who will survive, but the one that best responds to change.”

Tape for Changes

Unit Testing shouldn’t be a task that take your whole day on figuring out how to test a feature or setting up a fancy test tool(I’m talking to you Jasmine+Karma+PhantomJS). Instead, it should be a marker on the map, and a compass that guides you to create the next revolutionary app. Unit Test should tell you where you are and where you wanna go.

Most developers I’ve worked with that were introduced to Unit Testing drop off that practice and go back to the misbehaving lights in the metal rectangle. Most of the time due to over complication of tests, which made them spend more time solving testing issues than developing features.

Simplicity is something that should be part of your mindset when writing tests and any piece of code by that matter. And simple, is a word that describes Tape pretty well.

Tape is a unit testing library that provides out of the box a test runner, an assertion library, and formatted results using the standard TAP(Test Anything Protocol). That’s it, no describe(), no worrying about beforeEach or afterEach hooks, no globals polluting the scope, no headless browser, nothing to be concerned other that setting expectations and comparing against actual results. Dead simple.

The Codez…

Enough philosophy and let’s fast forward where the action is. Let’s build a Doge Clicker application.

The initial requirements will be:

  • Have a Doge image that when clicked quotes : “So Image, very test , much clicks #, wow” where # is the number of clicks to Doge.

And so the test begins…oh, but first, install all the tools we will need:

~$ npm install -D tape faucet babel webpack babel-loader

You should end up with a package.json config like this one:

The TDD cycle goes like this: Test > Fail > Pass > Refactor. So…

First Test

Fail

~$npm test> babel-node ./test/index.js | faucet
module.js:338
throw err;
^
Error: Cannot find module ‘../doge-clicker’
at Function.Module._resolveFilename (module.js:336:15)
at Function.Module._load (module.js:278:25)
at Module.require (module.js:365:17)
at require (module.js:384:17)
at Object.<anonymous> (/home/maredith/projects/taping/test/doge-clicker_tape.js:1:24)
at Module._compile (module.js:460:26)
at normalLoader (/home/maredith/projects/taping/node_modules/babel/node_modules/babel-core/lib/api/register/node.js:199:5)
at Object.require.extensions.(anonymous function) [as .js] (/home/maredith/projects/taping/node_modules/babel/node_modules/babel-core/lib/api/register/node.js:216:7)
at Module.load (module.js:355:32)
at Function.Module._load (module.js:310:12)
not ok 1 no plan found

The test failed and that is how it is supposed to be. As I mentioned before, unit tests are a compass that let us choose the right direction by coding expectations up front. If you read the comments in the test code, some design decisions were already taken before even writing any functional code. This is why TDD is also known as Test Driven Design.

Pass

Now let’s write the minimum amount of code to make the test pass:

//doge-clicker.js
export default function doge(){
return { count: 0, click (){ this.count = 1;
}
}
}

Run your test again…

~$ npm test
> babel-node ./test/index.js | faucet
✓ The Click should increase Doge counter by 1
# tests 1
# pass 1
✓ ok

Yay, green test! so far, no refactor is needed. Let’s test for multiple clicks:

test('3 clicks should increase the counter to 3', expect=>{
const wow = doge();
wow.click();
wow.click();
wow.click();

expect.equals(wow.count, 3, '1,2,3 wow');
expect.end();
});

Run test:

~$ npm test
> babel-node ./test/index.js | faucet
✓ The Click should increase Doge counter by 1
⨯ 3 clicks should increase the counter to 3
not ok 2 1,2,3 wow
---
operator: equal
expected: 3
actual: 1
at: Test.<anonymous> (/home/maredith/projects/taping/test/doge-clicker_tape.js:19:12)
...

# tests 2
# pass 1
⨯ fail 1

Fail! nice!! now we code to make it pass. This one is 1 character fix:

~$ npm test> babel-node ./test/index.js | faucet✓ The Click should increase Doge counter by 1
✓ 3 clicks should increase the counter by 3
# tests 2
# pass 2
✓ ok

And just when we think we’re done the boss comes and says:

Boss — “Remember that Doge clicker requirement? well, now we need that on every click the count should increase by a specific amount. Could you do that change this evening? We needed it for yesterday.”

You — “But it was requested today…”

Boss — “Exactly…”

Well, let’s keep going. Fortunately, the tests are strong within this one.

test('click(2), click(3), click(5) should increase the count to 10', expect =>{
const wow = doge();
wow.click(2);
wow.click(3);
wow.click(5);
expect.equal(wow.count, 10, 'Much click, so number, wow');
expect.end();
});

Run test and Fail, you know the drill. Now code:

export default function doge (){
return {
count: 0,
click (times){
this.count += times;
}
};
}

Run test and woooow…WTH??

~$ npm test> babel-node ./test/index.js | faucet⨯ The Click should increase Doge counter by 1
not ok 1 Wow I can count to 1
— -
operator: equal
expected: 1
actual: NaN
at: Test.<anonymous> (/home/maredith/projects/taping/test/doge-clicker_tape.js:9:12)

⨯ 3 clicks should increase the counter by 3
not ok 2 1,2,3 wow
— -
operator: equal
expected: 3
actual: NaN
at: Test.<anonymous> (/home/maredith/projects/taping/test/doge-clicker_tape.js:19:12)

✓ click(2), click(3), click(5) should increase the count to 10
# tests 3
# pass 1
⨯ fail 2

Refactor

Seems like our small change actually broke previous implemented functionality. Now we are forced to specify a number to every click() we execute. Let’s fix that by refactoring our code in order to keep the previous feature we had, since it’s still a valid requirement.

export default function doge (){
return {
count: 0,
click (times){
this.count += times || 1;
}
};
}

If we run our tests again…

✓ The Click should increase Doge counter by 1
✓ 3 clicks should increase the counter by 3
✓ click(2), click(3), click(5) should increase the count to 10
# tests 3
# pass 3
✓ ok

Now we are back on track.

If you want to see the complete project, clone this git repo and follow the source code. I have added more test samples with comments so you can tell what I was thinking at the moment of coding.

“How much test is enough?”

That is a very frequent question I’ve got asked, and my answer always is:

You’re never ready. You go when you’re ready enough— Colonel Graff

As I stated at the beginning, writing test should not be a very time consuming tasks. You should be ready to move to the next thing on development work once you feel confident of your test suite.

Code coverage tools were created for this purpose. BlanketJS, Istanbul, Isparta(Istanbul for ES6), JSCoverage, Coveralls.io are some of those great modules that can be integrated to your development workflow and CI process to give you that confidence on your tests. You should always target 100% coverage.

As an example, you can notice in the projects package.json a script.cover that uses Isparta to report coverage analysis of the our code base.

My last advice could be the simple rules I follow when coding:

  1. No more than 40 SLOC per file. Why? Eases reading and helps you to keep focus on simplyfing functionality.
  2. If a feature is hard to test, then it’s too complex. Time to break it down to simpler functional modules.
  3. Test happy path first, only then you should look to test responses to possible error scenarios.
  4. Keep mocks and stubs at its minimum(Database, FileSystem, Web APIs). If your module has too many dependencies, that’s a smell indicating it is doing too much. Go back to suggestion 2.

The Take Away

Changes to our software apps can appear at any time. Also, we know our code sometimes needs to be changed to meet some performance or maintainability goals. These changes would be an equivalent to drive your car and suddenly turn the steering wheel because you were about to miss your exit… better buckle up. Unit Test are our seat belts.

We saw earlier that a change introduced suddenly made us broke previous functionality. In a small and non realistic app, like this one, it’s really easy to spot where the mistake was. But in big, monolithic applications with hundreds of modules… it’s a different story…a horror story.

Embrace Test First development. It takes discipline and effort, but its really worth it. Once you get the grasp of it, you won’t feel the same writing code without tests backing you up.

Thanks for reading this long. Feel free to add comments and recommend if you think this writing was useful or at least entertaining.

Join me on the next article where I will cover Test First Development using React, Flux, and Tape to build a funny application.

--

--