How to avoid the benefits of unit testing and continue to code as a decade ago !
I don’t consider myself as a software craftsman, nor a good programmer but there are a number of good practices that I try to do/follow in every project. One of them is Unit Testing. When I started programming a decade ago, it was not so popular in dotnet or in web, and it was very rarely used. The first version of NUnit was released near 2003. Do you know that GitHub.com was deployed more than 10 000 times in 2013 ? Without a strong testing strategy (not only unit testing), it’s unreachable. This concept is not so new, but thanks to agility, TDD, continuous delivery, … it is much more important nowadays.
The thing is that I (like many others ?) was not educated to write unit tests : at the beginning of my dev life, I prefered starting my web site using debugging, inserting alert/Message box into my code, adding konami code …. I don’t really know how juniors think about it, but I hope it’s better than for me. As I try to become a better programmer every day, I progressively adopted unit testing, not only because it’s the natural way to work, but because I feel more confident about my produced code. Thanks to latest version of visual studio, it has never been so easy to write unit tests; but it’s only a small part of the job : You still have to write a few (a lot ?) unit tests and this is exactly the real problem.
It’s overwhelmingly easy to write bad unit tests that add very little value to a project while inflating the cost of code changes astronomically. – JUnit
So, here is a list of “bad” things you should carry on doing if you really want to miss out on all the benefits of Unit Testing and if you consider this practice to be a luxury. Warning : you may find here something you’re already done. I will be glad to add your ideas here. There are many things to say on unit testing. Everything won’t be listed here, but I think it’s a starting point.
- Dev First, Test After
Do you really think you will have the time at the end of your project? Thinking of unit testing during dev phase will also help you to write better –and testable- code. Validate/Approve your code with tests and not with UI/App or manual tests.
- A piece of code is not concerned
- Test useless, irrelevant or multiple things
It’s counterproductive to Assert() everything or anything that’s also asserted by another test. TDD folks express this by saying “have only one logical assertion per test”.
- It’s just a test
Consider your test code as production code because you will have to maintain it. Apply the same quality rules.
- Test Implementation
Never ! Consider the code unit as a black box : A good unit test manipulates the dependencies and the input of a class and verifies the output. The output can be a return variable or a verifiable call to another class (through mocking).
- Comment/Remove a unit test
Remove a unit test only when you have a very good reason. Less tests = more potentials regressions. “it doesn’t build” is not a good reason.
- Integrate DB queries, WCF
It’s simply not a unit test but an integration test; it’s important too. A good unit test tests only a small unit of the functionality of your class. If you are implicitly testing other classes, you are suffering from dependencies in your code.
- Dependent unit tests or random failures
Make each test independent to all the others. Nothing is more confusing than dependent unit tests : in one order all tests pass but not in another order. Also, running one million times a test should always give your the same result. Be very careful with truly random test data or with time-dependent code.
- It runs – only- on my machine
There are no problems to add as many dev dependencies as you want but it should be correctly referenced and install procedure have to be clear. An anonymous dev should not spend more than 5 minutes to setup environment. This will also help you a lot when using a build server. Avoid Hard coded sockets/file paths/IO too.
- Mister 100% Code coverage
Code Coverage is an important indicator… but not an objective. Coverage percentage use one or more criteria but basically, it checks if a line has been executed. Depending on locals, there may be many ways to execute a piece of code, so many tests to write. Your job is to validate behaviors, not executions.
- Use Sleep to simulate latency
Test should be fast. A simple sleep of one second in 200 tests will take more than 3 minutes when running all your tests (without concurrency). The problem with waiting ‘long enough’ is that any number of events outside your control could cause this sort of testing to fail.
- Use Wrong naming convention / You have to view code to understand the test
Name your unit tests clearly and consistently. When a test pass or fail, you should understand what it tests only by reading the test name. The minimalist naming convention is [CodeUnit_StateUnderTest_ExpectedBehavior]
- You have to scroll down to read the whole test
A unit test should be intuitive and small. Having too many things to setup before testing the actual thing affects reading. Everyone should be able to understand what the Unit Test is doing. It becomes hard to justify more than 25 lines of code for a test method.
Do not blame yourself if you did one (or more) of them. It’s time to change and any test is better than nothing. But it’s important to be in the good way as Martin Fowler said :
Whenever you are tempted to type something into a print statement or a debugger expression, write it as a test instead.
To summarize, when writing unit test keep in mind those famous properties: Understandable, Maintainable, Repeatable, Necessary, Granular and Fast.