This is the 2nd post on test attributes that were described in the now famous “Test attribute – Introduction” post.
We often forget the most of the value we get from tests come after we’ve written them. Sure, TDD helps design the code, but let’s face it, when everything works for the first time, our tests become the future guardians of our code. Once in place, we can change the code, hopefully for the better, knowing that everything still works.
But if (and when) something breaks, there’s work to be done. We need to understand what worked before that doesn’t now. We then need to analyze the situation: According to what we’re doing right now, should we fix this problem? Or is this new functionality that we now need to cover with new tests, throwing away the old one? Finally there’s coding and testing again, depending on the result of our analysis.
The more we move further in time, the tests and code get stale in our mind. Then we forget about them completely. The cost of the changes rises. The analysis phase becomes longer, because we need to reacquaint ourselves with the surroundings. We need to re-learn what still works, and what stopped working. Even if we knew, we don’t remember which changes can cause side effects, and how those will work out.
Effective tests minimize this process. They need to be readable.
Readability is subjective. What I find readable now (immediately after I wrote it), will not seem so in 6 months. Let alone to someone else.
So instead of trying to define test readability, let’s break it down to elements we care about, and can evaluate.
What A Name
The most important part of a test (apart from testing the right thing) is its name. The reason is simple: When a test breaks, the name is the first thing we see when the test fails. This is the first clue we get that something is wrong, and therefore, it needs to tell us as much as possible.
The name of a test should include (at least) the specific scenario and the expected result of our test. If we’re testing APIs, it should say that too.
For example:
@Test public void divideCounterBy5Is25() { ...
I can understand what the test does (a scenario about dividing Counter), the details (division by 5) and the expected result for this scenario (25).
If it sounds like a sentence – even better. Good names come from verbally describing them.
It doesn’t matter if you use capitalization, underscore, or whatever you choose. It is important that you use the same convention that your team agrees on.
Names should also be specific enough to mentally discern from other sibling tests. So, in our example:
@Test public void divideCounterBy0Throws() { ...
This test is similar enough to the first name to identify it as a “sibling” scenario, because of the resemblance in the prefix. The specific scenario and result are different. It is important, because when those two will appear together in the test runner, one fails and one doesn’t, it helps us locate the problem before even starting to debug. These are clues to resolve the problem.
What A Body
If our names don’t help locate the problem, the test body should fill the gaps. It should contain all the information needed to understand the scenarios.
Here are a few tips to make test code readable:
- Tests should be short. About 10-15 lines short.
- If the setup is long, extract it to functions with descriptive names.
- Avoid using pre-test functions like JUnit’s @Before or MSTest [TestInitialize]. Instead use methods called directly from the test. When you look at the code, setup and tear down need to be visible, otherwise, you’ll need to search further, and assume even more. Debugging tests with setup methods is no fun either, because you enter the test in a certain context that may surprise you.
- Avoid using base test classes. They too hide information relevant for understanding the scenarios. Preferring composition over inheritance works here too.
- Make the assert part stand out.
- Make sure that body and name align.
Analysis takes time, and the worst kind (and slowest) requires debugging. Tests (and code) should be readable enough to help us bypass this wasteful process.
Better Readability In Minutes
We are biased and so think that we write code and tests that are so good, everyone can understand it. We’re often wrong.
In agile, feedback is the answer.
Use the “Law of the Third Ear”. Grab an unsuspecting colleague’s ear, and pull it close to your screen, and she can tell you if she understands what the test does.
Even better, pair while writing the tests. Feedback comes us a by product and you get better tests.
Whatever you do, don’t leave it for later. And don’t use violence if you don’t need to.
Make tests readable now, so you can read them later.
Next up: Speed.
For training and coaching on testing and agile, contact me.
0 Comments