TDD: High Coupling vs. Low Coupling Tests
When writing tests, it’s not enough to “just have tests.”
🚫 High coupling – Tests coupled to Structure ➔ Fragile Tests
Do NOT write tests as specifications of your implementation.
When your tests know too much about the system’s internals (specific classes, methods, or dependencies), the tests become tightly coupled to the implementation, i.e. the internal structure.
If you change an implementation class, the test will break. This kind of testing leads to fragile tests.
🔍 Example symptoms:
Tests instantiate internal classes directly
Tests depend on method names or call chains
Small refactorings cause test failures even when behavior hasn’t changed
💥 The result:
Refactoring becomes risky and slow
Developers fear touching the code
Tests no longer describe behavior — they describe implementation details
✅ Low Coupling – Tests coupled to Behavior ➔ Stable Tests
DO write tests as requirements of the system.
Tests should communicate with the module’s public API and test the module’s external behavior. A module consists of a group of classes, where one class acts as the public class (exposes the module’s public API, it’s the entry point to the module) and the other classes are internal classes (they are implementation details of the module).
When our tests don’t know about implementation (treating it as a black box), we can change the internal implementation and we’re not going to break the tests.
We can do as much refactoring as we want, and the tests will not break.
✅ Characteristics:
Tests verify the module’s behavior, not its internal implementation.
Internal classes can change freely without breaking tests.
You can refactor safely and move fast.
💡 Example:
Your tests call
OrderService.placeOrder()instead of directly testing the underlying repository, entity, or validation class.If you refactor
OrderServiceor your validation class, the tests still pass — because behavior hasn’t changed.


