Skip to content
bebraw edited this page Apr 21, 2013 · 7 revisions

It is not possible to prove (easily at least) absence of bugs. You can, however, show that there are bugs in your code. This is where testing comes handy. Let's start by defining behavior of an assert function. As JavaScript doesn't come with an assert statement by default I've defined one myself. In case you are using Node, consider utilizing assert module.

function assert(s) {
    if(!s) throw new Error('Assertion failed!');
}

Unit Testing

Now that we have a function we can test with, lets define a couple of unit tests for a function known as title. You may recognize it from some other languages you have used before. Strangely vanilla JavaScript doesn't come with an implementation. So it is up to us to step up to the plate and remedy this vile omission. You can see a specification of sort below.

function title(s) {}

assert(title('HELLO WORLD!') === 'Hello World!');
assert(title('Hello world!') === 'Hello World!');
assert(title('hi') === 'Hi');
assert(title() === undefined);

As you can see title is simply a function that converts the first character of each word into a uppercase one while the remaining characters will remain lowercase. Now would be a good chance to go and implement it. Or if this is trivial and below your skills, consider the solution below:

function title(s) {
    // idea: split, set first to upper return joined
    var capitalize = function(k) {
        return k[0].toUpperCase() + k.slice(1).toLowerCase();
    };

    return s && s.split(' ').map(capitalize).join(' ');
}

It is likely your implementation will differ from mine somehow. As you have probably noticed so far I really like to use functional features of JavaScript. You can find a map there. I also used && (and) to deal with possible invalid s.

The point of this little exercise was to demonstrate the usefulness of tests. Granted the tests weren't as good as they could have been and the implementation was sort of trivial. Still, I hope you can see the value. This was an example of unit testing. I have coined the other type of testing that complements this well as generative testing. It is a perhaps underappreciated way of testing that can reveal issues you might not be able to detect otherwise.

Generative Testing

Unit tests are just that, units. Each unit test tests a specific thing. What if we forget to write a test? How do we know which unit tests to write and when? Let's just say experience helps in these things. Fortunately there are some ways that complement this well and allow us to overcome some of the weaknesses.

What if we defined the tests of title is a bit different way? First of all we know intuitively it makes sense to pass only Strings to title. I also mumbled something about the fact that each word has to begin with an uppercase character whereas the remained must be lowercase. By combining these observations we can end up with something quite interesting. A way to test our little function in a generative manner.

As I don't want to explain how to build the testing tools, I will rather use some experimental tools of my own. The most important one of these is simply known as annotate. It is a tool that allows you to attach annotations, and more importantly, invariants to your functions. I've annotated title below.

var title = annotate('title', 'Returns given string in a title format.')
    .on(is.string, function(str) {
        ... // original implementation goes here
    }).satisfies(is.string);

I use another utility, annois, to define that the function input is a string. Besides this precondition I defined a postcondition to make sure that you will get a string out as well. This might remind you of Eiffel's concept of Design by Contract. Congratulations! If not, don't worry. You'll see what the fuzz is about in a bit, literally.

Currently the value of what we're doing might seem a bit dubious. We just ended up with more code. That was not the point, though. In order to benefit from the extra detail, we'll need to add second part. An external invariant. Consider the definition below.

fuzz(title, function(op, str) {
    var parts = op(str).split(' ');

    return parts.map(fst).map(isUpper).filter(id).length == parts.length;
});

function fst(a) {return a[0];}
function isUpper(a) {return a === a.toUpperCase();}
function id(a) {return a;}

fuzz is a special kind of function. It uses our function preconditions and the defined invariant to construct tests. It will do its best to try to invalidate it. Can you see how much powerful this new test of ours is?

In case you are interested in this sort of testing, check out my testing tool, annofuzz, that implements this idea.

Fibonacci's Numbers Using annotate

Just to give you another example of the power of annotations, consider the example below. It shows you how to implement a function that yields a series of Fibonacci's numbers. The definition is quite close to the original and reads well at least to my eye.

var fib = annotate('fib', 'Calculates Fibonacci numbers')
    .on(0, 0).on(1, 1)
    .on(is.number, function(n) {
        return fib(n - 1) + fib(n - 2);
    });

I use this form of testing quite a bit at a library of mine, funkit. Unfortunately generative testing hasn't quite gained widespread acceptance within the JavaScript community. For instance the Haskell guys are way ahead of us with their QuickCheck. There are implementations available for other languages as well and it is quite easy to port the idea.

These two ways to test are handy for validating some basic assumptions on individual functions. That is only small part of the field. You might for instance want to know how your code behaves on various platforms. This is where browser testing comes in.

Browser Testing

So far we might have run the tests through some browser or environment such as Node.js. How can we be sure our code works in multiple browsers for instance? That is where services such as browserling, testling, BrowserStack and Travis come in.

I won't delve into details. Suffice to say the services mentioned make it easier to leave testing to machines. You definitely don't want to have to check all browsers yourself. You are better off spending that time on something else.

Conclusion

I hope this brief chapter gave you some idea about how to test your JavaScript code. Rather than having to develop a framework of your own I recommend checking out some established alternatives and ones to use based on your needs.

In case you end up developing a framework of your own, check out Test Anything Protocol. Using that as a format allows your testing tools to hook into a larger testing ecosystem. You can display test results very easily using some existing parser for instance.