Home > aen's Blog

Unit Testing Mu, Part 2

No foray into MuBitmap and MuSprite today, as I found some heady new waters to wade in!

Thanks to feedback from a friend (or two!) concerning yesterday's entry, I spent the last few hours reading over a few great articles covering Test Driven Development in the context of games programming.

I was eager to assimilate all of the information, and read straight through all three. There are quite a lot of useful insights in there! I was so pumped after the article I decided to spend my time tonight refactoring the tests I created for Mu yesterday.

mu, build 11

The first thing you'll notice is that the number of tests have grown from 41 to 60. This was the end result of making my tests much more granular and specific. I had thought my initial tests were fairly granular, but after reading Noel's articles, I realized I wasn't being as granular as I should be.

Noel advocates very small tests, the smaller the better. The quicker the better too, since it's entirely likely you will end up with hundreds if not thousands of them in projects of moderate size. When you have that many tests, it's important they run as quickly as possible, so as not to make the build process an irritatingly time-consuming one.

With this realization that tests should run quickly, I removed several tests which iterated over several hundred or thousands of items, either for creation or equality checking. After thinking about it a little, it seemed silly to do so many iterations. A couple iterations, a few at most, are probably more than sufficient. If I ever end up suspecting otherwise on down the road, I can write a new test then.

Another great bit of advice I took away from these articles was the manner in which tests were named. For example, Noel illustrates that a test name TestArmor is much more obscure than one named ArmorHasCorrectRechargeRate.

Test names should indicate what they intend to test as clearly as possible, and names that make a statement in plain English go a long way towards self-documentation. Who cares if the test name is a mile long? If it reads well, let it ride. Unit testings frameworks, such as JUnit, automate the discovery and execution of your tests, so it's not like you're going to be calling these methods yourself. It's better than putting the statement about what is being tested into a comment which may become outdated as tests are refactored.

I renamed pretty much every single test method I had. I had to keep the test prefix on them all, since that is part of how JUnit determines if a method is a test or not, but it doesn't make anything less clear. I now have such descriptive test names as:

testColorWithNegativeBlueIsNotAllowed
testLargestPossibleCoordinatesGoInAndComeOut
testCannotSetFirstElementOfAnEmptySet
testConsistentCountWhenPoppingMultipleElements

I like it!

The MuSetImpl and MuStackImpl tests were again very similar, and I even initially copied all of the rewritten tests for MuSetImpl into the MuStackImpl test area, then tweaked them all. I'm not sure if that's necessary, but for now I'll let it sit.

The stack did require an additional test for popping functionality, and in the course of copying the method for removing items, I encountered some of my first test failures! Which was nice, since everything up until now had been so trivial I got them all "right" the first time.

The discrepancy was in copying and tweaking this test from the MuSetImpl tests:

public void testCannotRemoveFromEmptySet() {
    MuSet a = new MuSetImpl();
    try {
        a.remove(0);
        fail("able to remove first set element when no elements exist!");
    } catch (IndexOutOfBoundsException exc) {
        assertTrue(true);
    }
}

I initially tweaked this into the following:

public void testCannotPopFromEmptyStack() {
    MuStack a = new MuStackImpl();
    try {
        a.pop();
        fail("able to pop first stack element when no elements exist!");
    } catch (IndexOutOfBoundsException exc) {
        assertTrue(true);
    }
}

When I first ran this test, it was failing. For some reason pop() was succeeding with no elements in the stack. Upon investigating the definition of pop(), I found I had implemented it like so:

public E pop() {
    return count() < 1 ? null : remove(count() - 1);
}

At the time I had initially written it, I didn't put too much though into it, and it seemed acceptable that it would return null if there were no elements present. Now that I was thinking more critically about the situation, I realized popping behavior was somewhat similar to iteration in that you're requesting that some action be taken on the next available element, in this case the very last one.

When you try to call next() on an iterator that has no elements left to give you, it will throw a NoSuchElementException. And this makes sense to me, since you shouldn't be requesting actions on elements which don't exist. If you are, your code is broken. Just "silently" returning null is a rather lax (i.e., sloppy) approach and only sets you up for latent odd behavior which may be difficult to track down when you should've been following the best practice of "failing fast." If something's wrong, it's best to know about it sooner rather than later.

This comparison at hand, I modified my popper to throw an exception accordingly:

public E pop() {
    if (count() < 1) {
        throw new NoSuchElementException("no elements to pop!");
    }
    return remove(count() - 1);
}

Then I modified my test to catch NoSuchElementException instead of IndexOutOfBoundsException. Run test. Test passes. Win!

A fairly minor catch, to be sure, but I certainly begin to see how unit testing keeps you in check. A lot of these little minor oversights can come back to haunt you. A good test will vanquish the demon at the gate, before it usurps your productivity! You are forced to, as Noel humorously puts it, "eat your own dog food."

I got a kick out of the reference to Extreme Programming as "extreme dog food eating." He makes good sense though, and I look forward to eating as much dog food as I can keep down!