Unit Tests - Help them, Help you

Unit tests are sometimes thought of that cousin nobody likes and everybody can't wait for him to leave, although he gets invited regularly because, well, what are you going to do? - He's family.
Whether we write unit tests because we believe in them, or because somebody insists that we do (be it your team leader, some agile guideline, the Pope, etc.), there are ways to make the end result more effective, and dare I say - fun.

If we reach deep into out hearts and think about it, we may arrive at the conclusion unit tests are just as code as our production modules (if you still don't think this way, you must have not reached deep enough). Well, almost, but close enough to apply most of the software engineering best practices when we write them. Unit tests are here to assist, so help them, help you.

With time, I've found the following pointers to be very helpful, though heavily under practiced:

  • Give tests clear names - there seem to be quite a few naming conventions for tests out there, I've been practicing the Test_Condition_Result template and it does the trick. It may turn out to be a bit lengthy, but given enough patience to read it through  - one knows what's being tested, and what the expected result is. Both are priceless when it comes to testing.
  • Have domain specific asserts - assertions are key to understanding what is considered as a desirable outcome by a given test. Encapsulating non trivial assertion logic into your own assert methods makes tests considerably more readable. For instance, you could have a AssertPlayerMovedLeft(..) instead of having a battalion of primitive asserts inline asserting that your player has indeed moved left (one can think of it as yet another "Extract Method" refactoring, only for assertions).
  • Randomize data, not behavior - randomizing things in unit tests is pretty common in order to provide a wider coverage than simply testing one particular scenario out of many possible ones. However, while randomizing input data may be a nice touch, randomizing behavior (i.e., flow) is essentially taking the determinism away, which is seldom a good thing. Non deterministic code is extremely hard to debug, and bugs usually require quite some efforts to reproduce. From my personal experience, I recommend avoid using "if(random.NextBool())". This is especially painful if you have a few of these in one test, since when it fails, it becomes very frustrating to try and reconstruct the flow that took placeIf the alternate flow is important enough, consider writing a separate unit test to deal with it.
  • Write concise and readable code - making code readable is hardly a new goal in software engineering, but it feels like it's especially important when it comes to unit tests, as they tend to be less coherent and readable than typical production level code (whatever that standard is to you). I always try to practically abuse the "Extract Method" refactoring when writing tests, in order to make it's body as clear and concise as I can. If the reader can understand what's going on in a test in a glimpse of an eye, the phrase "Always code as if the person who ends up maintaining your code is a violent psychopath who knows where you live" is of no threat to me. On the contrary, we may end up going for a beer after work.





Comments

Popular posts from this blog

Eclipse vs. IntelliJ, an empirical rant

Reflecting on reflection in Scala and Java