I like to review tests. It’s one of my main learning experiences. You can learn so much from the way tests are written: how the developer thinks, even how the team works together.
When reviewing there’s an opportunity for improvement. Our tests are there to help us pinpoint a problem and fix it as quickly as possible. Yet, we can mess them up front, so they drag as along later. Here are a couple of points you might want to look for the next time you review a test.
Obscure Test Names
For example: test33 or GetCountTest.
Tests are our first line of defense. When they fail, the first thing we look at is their name. The problem with these names is that they don’t help you fix the problem quickly. You need to start debugging from the top, which is not a very effective use of your time.
Consider a name like GetCount_NoItemsInList_ReturnEmptyList. Now we’re getting somewhere – we know what we’re actually testing, the specific scenario and what didn’t happen when we ran it. This is a good starting point. Maybe just by looking that the code you can understand what happens, even without debugging.
No Asserts
The assert statement is the only reason a test should fail. It is where you explicitly express what the pass or fail criterion for the test is. Without it, you risk the test passing or failing because of external issues (for example, an exception getting thrown) that don’t have anything to do with the logic you want to check. Trust me, you don’t want to start your inevitable debugging session with “ok, what happened here?”. Along with the name, your assert statement gives you a shortcut for understanding what went wrong and how to fix it.
“But my test checks that there are no exceptions, it should pass if there aren’t any exceptions.”
The intention is good. And it usually works when running the test for the first time. But what happens a year from now, when someone (hopefully not you) looks at that test and asks: what does it test exactly?
Let’s assume that you’ve named your test like: GetCount_EmptyList_NoExceptionThrown, (because if you didn’t you’re sending the poor spectator on a very wild goose chase). What happens when the test fails? Obviously an exception happened, but now what do I do? You should be as clear and specific as possible, and you do that by using an assert.
Many Asserts
These tests usually come with obscure names (see point #1). If the test writer doesn’t have a clear purpose in mind when writing the test, it becomes apparent when he tests for 5 different things. If you start with naming your test correctly, you’re probably not going to get there.
“You told me to put asserts in, so what’s wrong with 10 of them? I’m just saving the rewriting of the setup 10 times.”
Tests are most effective when they are readable. If a test asserts many things, it doesn’t direct me to what went wrong. Along the way, you’re making me read a longer, often complex test. One assert pinpoints the problem, many asserts hide it. Also, if you test many things, any change in them can break the test. We don’t want to spend our time rewriting tests. We’d rather spend it on production code.
“Is it ok to test 10 properties on one object? It’s not 10 different things, it’s like a deep assert”.
This calls for further investigation. Do you really need to specify asserts for all 10 properties? Or just the 2 important ones? The test should fail on the important stuff, and should not keep you busy with the fluff. Think before you decide on any assert.
Want to learn more how to write effective tests for legacy code? Join Typemock’s webinar this Wednesday. Register now– space is limited.