Sunday, August 20, 2017

Fast & Dirty Minimal Unit Testing

When we coders first encounter the concept of unit testing, it's usually in the context of some kind of instruction: a class or an article of some sort, maybe a video or a podcast. Most often, the instructor will demonstrate the concept of unit testing with the use of a testing framework. In Javascript, that might be QUnit or Mocha. In Java, JUnit. Etc. While a testing framework is fine, configuring it and actually using it can be a bit of a task, especially if you're doing something interesting. Sometimes you just want to code, code, code, not bean count, am I right? You're anointed. You're zooming. Ain't nobody got time for all that!

Friends, I'm here to tell you that you don't need no fancy testing framework to do unit testing! You can test right as you zoom along. Here you go. Here is your essential, no-frills framework:

const doTest= (message, result, expected) => console.assert(expected == result, message, result); // port as necessary to the language of your choice. // or change == to === as you wish.

Use it like this:

doTest("squareIt(10) should be 100 but was ", squareIt(10), 100);

Your first argument is a message ("function should return x"); second argument is literally the function calling some value; and the third is the value you expect to get.  If the expected value does not match the result, then the console will show an error:

Assertion failed: squareIt(10) should be 100 but was 101

Simple. You can open your browser console right now to see it in action.

However, it probably needs one more iteration, because this will fail when comparing non-primitive-type objects like

([1,2,3] == [1,2,3]) == false

a==b
only works if a and b are primitive types.  But remember:  this is a minimalist framework designed to get you testing without interrupting flow.  When you need an upgrade, just go ahead and modify it as you see fit.  If you find yourself needing to compare arrays, for instance, go ahead and modify the doTest. One way to solve this is to borrow yourself a fine array comparator such as:

const isEqualArray = (a, b, i = 0) => a.length != b.length ? false : i >= a.length ? true : a[i] == b[i] ? isEqualArray(a, b, i + 1) : false;

and then modify your unit test function so that  doTest checks to see if the result is an array, and then runs the isEqualArray function.

const doTest = (message, result, expected) => { const isEqual = (result instanceof Array) ? isEqualArray(result.sort(), expected.sort()) : result == expected; console.assert(isEqual, "\n" + message + "\nReceived:", result); };

Don't forget to add a unit test to make sure it's working:

doTest("Comparing the same arrays should pass: ", [1,2,3,4],[1,2,3,4]);

Boom! Your framework now compares arrays.

With doTest in place, it's easy to drop a test anywhere meaningful.  When you find yourself thinking "This function should be returning x but it's returning y.  Hmm." Just  doTest("f(x) => y", f(x), y) Then you can get to work solving the problem with a permanent, reliable partner keeping your goal on track. Later, if some fix has broken something critical, you can pin down more easily what part is broken.

doTest('complicatedFunc("What is going on??") should be "Sssh, Pumpkin. It's okay." but is '...

Also, I recommend throwing this one at the head of your list of tests:
doTest("This test should always fail.", false, true);
This will tell you that your test suite is working as expected and make your heart skip a beat at random moments. Always fun.

As a final note, obviously there are times when you will need a more sophisticated testing framework. But if you find yourself leaving constructing unit tests until the end, give this 'roll your own' approach a try. You can always easily port your tests to another framework whenever necessary.