Motivation
Hey TDD friend! This is FREE lesson from my upcoming Test-Driven Development course. I launch it next Monday on 3rd June with a 50% discount. The discount will be valid only for 3 days, so stay tuned.
You can’t have clean code without clean tests. Treat your tests as first-class citizens. Writing clean tests is as important as writing clean production code.
When I help teams to improve their testing practices, I always see the same 5 test smells. Here’s what they are, how to detect them, and how you can fix them:
1. Fragile tests
❌ Problem
Your tests fail to compile when you change the production code.
🔃 Cause
Your tests are coupled to low-level implementation details such as internal functions or classes.
As you see in this example below, the test is coupled to implementation details like FindById(..) and Store(..) methods.
✅ Solution
Don't couple your tests to implementation details or code structure. Instead, couple them to the behaviors of the public APIs.
The following test uses fake to verify state changes without being coupled to the internal functions:
2. Eager tests
❌ Problem
The test is hard to understand as it verifies too much functionality in a single test method.
🔃 Cause
Having multiple Act and Assert parts within a test.
The following test is an Eager Test because it covers multiple behaviors: bank account creation, deposit, invalid withdrawal, and successful withdrawal.
✅ Solution
You can fix the Eager Test smell by splitting up the test into multiple test cases. Each test should verify a single condition:
3. Mistery Guest
❌ Problem
The reader must look outside the test to understand the behavior being verified.
🔃 Cause
Some global context is used to initialize data for reusing objects and avoiding duplication.
In the following test, we don’t have any info about the User as it is initialized outside the test context:
✅ Solution
Include everything needed in the test to understand the tested behavior. The Don't Repeat Yourself principle is less important in test code. Prefer readability over removing duplication:
4. Test with irrelevant data
❌ Problem
It's hard to tell which data affects the test result.
🔃 Cause
There is too much irrelevant information in the test case.
✅ Solution
Don't pollute your tests with irrelevant test data! Irrelevant data increases noise and decreases readability. A test should read like a story with only the essential information.
There are two great ways to hide irrelevant data:
abstract irrelevant information into a method
use the builder pattern
5. Test with logic
❌ Problem
Tests are too complex to understand and prone to bugs.
🔃 Cause
You use logic in tests such as if-else statements, loops, or switch cases.
✅ Solution
Avoid any logic in your test code! Once you feel the need for these, it's a smell that you test more than one thing. You can get rid of test logic by splitting up your tests into multiple test cases.
Another option is to use parameterized tests to declare your test cases and to avoid duplication:
Conclusion
Writing quality tests is a skill worth its weight in gold. To master testing, I made a Test-Driven Development course, including:
Other test patterns and anti-patterns
How to write clean tests
The 5 different test doubles
The two schools of TDD
The power of mutation testing
3 real-world projects in C#, TypeScript and Rust, built with TDD
It not only points out the wrong but also shows the right approach in each example. Very well structured newsletter.
I only do not understand fully the 4th example, though. Should I change the existing or add a new implementation to construct an object with less parameters just for test clarity, in this example?
Well explained, @danielmoka. I loved the examples!