Home > aen's Blog

Unit Testing Mu, Part 1

I started implementing unit tests for Mu from the simpler classes and on up. So far I've got 41 tests spanning five of the less interesting classes. I placed all of the unit tests in the mu.test package, and the AllTests class is a test suite which runs them all. The JUnit testing framework is integrated into Eclipse 3.1, and it's pretty slick if you ask me!

mu, build 10

Technically, only three of those involved much thought since there are a lot of similarities between MuLocImpl, MuDimImpl and MuSetImpl, MuStackImpl.

Up until recently, I wouldn't have thought to test most of the things I'm testing for, the proper implementation of the equals() among them. I didn't know what the reflexive rule was (an object should equal itself,) or that it might be a worthwhile test to check for "consistency." That is, that the same result is returned after numerous calls to equals().

Even boundary cases I probably wouldn't have thought of, which involves testing that your classes handle the extreme values in their ranges correctly. Reading up on unit testings provides you with plenty of questions though, so I've been getting answers slowly but surely.

Some of my tests seem a little contrived, such as testEqualsMirror(), and some of them break the guideline of not calling tests within other tests, such as testEqualsConsistency(), but implementing them and implementing them the way I did gets me started down the unit testing road. If my tests are contrived, I should eventually be able to discern useless tests from ones of value. If they break with guidelines, they only do so because doing so seemed a simpler or more elegant solution, and I'll have to prove myself wrong.

Testing for consistency, for example, basically involves calling the same methods over and over. So it makes sense to me that I would call a bunch of the tests I already made in a loop in my consistency test. Maybe someone with more unit testing experience can tell me whether or not this is actually bad, and why!

I also use random number generation in some of my tests, which I'm not sure whether or not is a bad idea. Seems like it's just another flavor of testing, that kind of randomly pokes and jabs at the code I'm testing. I figure it may be helpful if for some reason certain values pop up that cause difficulties, and I had not previously included such combinations explicitly in my tests. If it's not the best idea, then at least it's a good exploratory tool to help me discover the proper way to think about testing so I won't have to use random tests in the future.

After writing tests for MuLocImpl and MuDimImpl, I moved on to testing MuColorImpl. A lot of the same equality tests in there, but with a vaguely more interesting test that checks that exceptions are thrown as they should be when attempting to create a color with components that are out of range.

Testing for exceptions is one of the more obviously beneficial tests to me. I can understand that, okay, my class should never throw these exceptions except under the circumstances X, Y, and Z. Luckily so far it has been easy to manufacture circumstances that trigger exceptions.

From various literature I've been reading though, it'll be time to really test my unit testing mettle when I need to manufacture circumstances such as database connection failures, network connection failures, drive space exhaustion, etc. I've read a bit about mock objects, but that seems like a potentially heavy undertaking! Guess I'll find out when I get there.

Next I moved on to the MuSetImpl and MuStackImpl classes. In these tests, I tried to test each method in the class, since I've read that's how many tests are written. To be very finite tests of all the methods of a class. If you know all of the methods of your class are functioning properly, then it follows that they should work correctly in a larger context when communicating with other objects.

I don't know if I'm in total agreement with that, but it's certainly easy to test on a per-method basis. Plus, larger beastly classes that are built up of other beastly classes will have methods that test interactions between many objects, so writing a test for a single method could cover any bigger-picture testing that would need to take place.

I must admit my tests are not original in the sense that I thought critically about what needed to be tested and came up with them on my own, but as with this entire exercise of unit testing, it's helping me to get into the groove. The ways in which I test the iterators, for example, were adapted from tests I encountered while reading:

Programming Ruby: The Pragmatic Programmer's Guide.

Tomorrow I'll be expanding the tests into the more interesting classes, such as MuBitmap and MuSprite.