Sunday, 1 January 2012

Write Unit Tests? Start deleting them

A recent blog post by Steve Klabnik concluded with a statement about tossing unit tests if you have end to end tests covering the code in question.

Don't be afraid to change the tests! As soon as you've verified that you've transcribed the code correctly, don't be afraid to just nuke things and start again. Especially if you have integration level tests that confirm that your features actually work, your unit tests are expendable. If they're not useful, kill them!

A few people on Twitter found this odd, and I'd have included myself in this statement a while back.

Kent Beck's TDD screencasts changed my view on deleting unit tests however. During the later videos, he actually deleted some tests. Pretty much all TDD resources don't really mention this. One of the key points beginners learn is that if you break any tests, you've introduced a regression. This is not always the case. If you follow the rule of never deleting ANY tests you encounter you are going to be stuck with someone else's implementation forever. Likewise unit tests are there to drive design, not enforce how something works. I remember discussing deleting unit tests with my work colleagues and finding Kent's videos pretty shocking at the time. I mean deleting unit tests!?

The more I do TDD, the less this statement becomes so jarring. For example.

Consider a test for the above behavior, such as we get the result back in a particular state. Pretend the logic is rather simple, and it does not warrant a separate object. Any other developer should be free to come along and change the internals of this method. As long as we get a result back in the correct state, the test should be valid. The test should not care that we are using strings, lists or whatever internally.

Occasionally I find tests like this hard to pass. In other words, I feel like the logic is correct yet the test fails. Maybe I'm using a new language feature, or a language feature that seems to be not working as I expected. If so I'll break out a new unit test that tests the implementation. Such tests are often refereed to as learning tests. Here with a smaller focus I often become aware of what I'm doing wrong. Following Kent Becks example, I ditch the test after and move on.

I feel this sums up my feelings nicely.

I and others are not saying bin every unit test you have that is covered by end to end tests. Unit tests are great, you can run hundreds in a matter of seconds. They have their place as part of the development process, but do not find yourself working against them. However I am saying you should delete any test which relies on implementation details. I am saying bin any test which does not make sense. I am also saying bin tests as part of a refactoring session as long as you have test coverage higher up. If you don't have test coverage such as acceptance tests, you cannot be sure you have not broke anything after the refactor.

Log Everything

This post was originally conceived back in mid 2011, starting a new project made me think back to this event, hence the post.

Any developer worth their salt will know what logging is. You could argue there are two types of logging, either developer logging or auditing. Developer logging would be what we log when something goes wrong. Using the results of this logging we can track down what went wrong, and put fixes in place to stop this event from occurring again. If this logging fails, or logs the incorrect thing it is not the end of the world. Due to this, I generally do not care for testing such scenarios. The code should behave the same with our without this logging.

Auditing would come under logging which as part of the application needs to be carried out at all times. Consider visiting a cash machine. When withdrawing fifty pounds, you want to make sure your bank logs this in case anything goes wrong. This sort of logging is crucial, and must work and must log the correct data. This is considered a feature, therefore this should be tested as it is part of the behavior of the application.

When I think back to my first few years of programming my code was littered with logging. In the early days after each statement, variable and function I would print out what happened, along with any errors that happened. In fact I'd say that everyone starts out like this. The strange thing is as we get better, the logging becomes less and less. Rather than the first class citizen we relied on in the early days, logging is seen as boring. The problem with treating logging code as a second class citizen is that when things go wrong, it can be very difficult or near impossible to track down what has happened. When you realise you need logging, its often too late. You will need to redeploy the application and wait for the problem to expose itself again.

Back in 2011 we had a difficult problem to track down. The dreaded "OutOfMemoryException". Being the cocky developers we were, we decided to add the logging last. After all, it was there for when things went wrong. We never planned things would go wrong, after all it "worked on my machine". After redeploying the application with logging we managed to track down roughly what was going wrong, and in turn began to resolve the problem. Had we added this logging initially, we could have resolved this problem in half the time.

The lesson I learned here was simple. Any time you have an error, log it. If the logging is not in place, we add it. Creating a new application? In the first iteration(s) make sure some form of logging is in place. I believe by following this simple rule any future issues can be handled easier. Logging should be a first class citizen regardless of purpose.