Your unit test methods need docblocks too

If you've met me at any time in the previous 20 years and you discussed unit testing with me, chances are pretty big that I'd have told you that your test methods in your unit tests don't really need docblocks, because the test methods would be named in such a way that they were descriptive. In a unit test class you could have methods such as testCheckWithQueueThatHasMessagesButIsNotProcessingThemReturns500() or testCheckWithQueueThatHasCorrectValuesReturns200(). Those names give you a good idea of what it is testing, which makes it a lot easier to find the test that fails when you run your unit tests and get some red Fs in your field of dots.

Tests can be hard to real though, especially (but not exclusively) when testing legacy code. You may have lots of mocks to configure, for instance, or you may have several similar tests that are testing specific situations, edge cases or bugs you found along the way that you wanted to test before you them. Even when you wrote the tests yourself, in 6 months you may not realize what the context was when you wrote the test. Or you might have another developer join the team that is not aware of the context.

Documentation is important. It lowers the bus factor, makes it easier to on-board new developers (or temporary external developers, waves) and makes you think about your own code in a different way. We're (getting?) used to documenting our software, but why not document our tests by giving it a bit more context?

It fixed a bug

Earlier this week I was writing tests for the code I had just written. I usually write empty test methods first for every situation I want to test, and then fill them up one by one. As I came to the last empty test method I looked at the situation I wanted to test. I implemented the test as I thought I had meant it based on the name of the method. Then I started adding docblocks to give the tests a bit more context. As I was writing the docblock for the last method I paused: Something was wrong. The thing I was describing was not actually the thing I was testing. Looking closer at the test, it made no sense. Everything I tested in this method had been tested in other methods.

I ended up rewriting the test to actually cover the situation I had wanted to test, and tweeted:

I started adding docblocks above test methods to describe what I'm testing. I just caught myself writing a nonsensical test that way. WIN. (@skoop)

What to document?

The way I write the docblocks is that I describe, in actual human understandable language, which situation the test covers. For instance, for one of the above examples:

/**
 * This test checks that the happy flow is correctly handled. 
 * If the queue returns the right data according to our
 * specifications, it should return a 200 response.
 */ 

This will give you a lot of information about the test. But this one is for the standard happy flow, so it's still short. Let's have a look at another one.

/**
 * This test checks the failure flow: Matching transactions 
 * fails. We also test whether database transactions are
 * used successfully: We should still commit the transaction
 * in this scenario
 */

Here I don't just explain the flow I'm testing, but I also explain some additional things we test in this specific test: Many developers would assume that in a failure scenario the database transaction should be rolled back, but in this specific case, it fails to match information, but that is an expected outcome, so we should still commit the database transaction.

Assumptions are... well, you know the drill. I realize that as a developer I make assumptions all the time, but if I can minimize the assumptions I (or other developers) make with only a small bit of effort by documenting those details, that's a WIN in my book.

DDT: Docblock Driven Testing

So these days as I start writing my tests, I still create the empty test methods first, but they are now immediately accompanied by the docblocks, describing in a bit more detail which situation the method is going to be testing. That helps me make sure I don't accidentally miss any possible scenario, or accidentally write a test I had completely meant to be different.