Skip to main content

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

Sending out Storm metrics

There are a few posts talking about Storm's metrics mechanism, among which you can find Michael Noll's postJason Trost's post and the storm-metrics-statsd github project, and last but not least (or is it?)  Storm's documentation.

While all of the above provide a decent amount of information, and one is definitely encouraged to read them all before proceeding, it feels like in order to get the full picture one needs to combine them all, and even then a few bits and pieces are left missing. It is these missing bits I'll be rambling about in this post.

Dependency Injection - The good, the bad and the ugly

The Good
Dependency injection (DI, a.k.a IoC - inversion of control) is a well known technique to increase software modularity by reducing coupling between modules. To provide the benefits of DI, numerous DI frameworks have arisen (Spring, Guice, Castle Windsor, etc.) all of which essentially give you "DI capabilities" right out of the box (these frameworks tend to provide a whole lot more than just "DI capabilities", but that's not really relevant to the point I'm about to make). Now, to remove the quotes around "DI capabilities", let's define it as a DI container - a sack of objects you can manipulate using a provided API in order to wire these objects together into an object graph that makes up your application.

I've worked on quite a few projects employing Spring, so it will be my framework of reference throughout the rest of the post, but the principles and morals apply just the same.