Unit Tests should NOT mirror code (Michaël Azerhad)
If your unit tests are mirroring your source code structure - one Unit Test per class, then you're doing it wrong.
Welcome to the free edition of Optivem Journal. I write about Modern Test Pyramid & Modern TDD practice to help you escape the nightmare of Legacy Code. I know you're stressed & don’t have time, and you don’t want to wait for several years. If you want support on your TDD journey, join our paid community of 170+ senior engineers & engineering leaders. Get instant access to group chat & live Q&As:
I highly value discussions with Michaël Azerhad, I find his writing highly resonates with my perspectives on TDD in practice. I highly recommend you follow him on LinkedIn (some posts are in French, some in English) about TDD & related topics.
“Unit does not mean « unit of code », does not mean « don’t traverse more than a function ; mock all neighbors », does not necessarily means "small".“
…
“A unit test describes a behaviour, a « denouement », a « business scenario », and therefore there must be a kind of contravariance between tests and code.
Unit tests do NOT mirror the code!”- Michaël Azerhad
There is a big confusion around Unit Testing (perhaps arising due to the Sociable Unit Tests vs Solitary Unit Tests approaches). So I collected some of Michaël’s posts below.
📢 My next Live Q&A Session about TDD & Unit Tests is on Wed 18th Jun 17:00. Here are some questions I’ll be answering based on Group Chat:
How to deal with unit tests & multithreading?
How to unit test abstract classes in libraries?
When we are testing Application Services (or Use Case Handlers) should we stub out Domain Services?
Which Test Doubles should we use in unit testing? (should we use mocking libraries or write our own test doubles)
Should we implement read methods on production repositories, just for test purposes?
How to handle non-halting errors?
Have an additional question? Add your question on Group Chat.
If you’re a Paid Member, you'll receive a 100% discount code (click on "Register now”; see Event Description for instructions). Upgrade to get the discount:
Most developers do NOT know Unit Testing
Michaël Azerhad wrote a LinkedIn post about the most crucial thing that developers still don’t know - they don’t know the meaning of unit tests. A Unit Test should describe a behavior, it should be coupled to behavior rather than being coupled to code. That’s why we should avoid one-on-one mirroring of code through tests (a mistake that many developers make, creating a unit test per source code class)
By avoiding the mirroring, this enables you to refactor code without breaking tests:
To learn more about the dangers of “mirroring“, read the “One-to-One Correspondence.“ in Uncle Bob’s TDD Harms Architecture.
“Most people who are new to TDD, and the three laws, end up writing tests that look like the diagram on the left. They create a kind of one-to-one correspondence between the production code and the test code. For example, they may create a test class for every production code class. They may create test methods for every production code method.
…
The problem is – and I want you to think carefully about this next statement – a one-to-one correspondence implies extremely tight coupling.
…
It, frankly, took me many years to realize this. If you look at the structure of FitNesse, which we began writing in 2001, you will see a strong one-to-one correspondence between the test classes and the production code classes….
And, of course, we experienced some of the problems that you would expect with such a sinister design. We had fragile tests. We had structures made rigid by the tests. We felt the pain of TDD. And, after several years, we started to understand that the cause of that pain was that we were not designing our tests to be decoupled.”
- Uncle Bob
Clarification about the word “unit“ in Unit Testing
Michaël Azerhad wrote another LinkedIn post to further clarify the word “unit“, explaining the difference between Sociable and Solitary Unit Tests. In Classical TDD, Sociable Unit Tests tend to emerge:
He further explained why we should use Sociable Unit Tests (which are coupled to behavior and may traverse multiple classes) rather than Solitary Unit Tests (which are coupled to individual classes - units of code), it’s because it enables emergent design:
Michaël explained the historical problem of the word “unit“:
The following is a screenshot from Ian Cooper’s presentation:
“I call them “unit tests“, but they don’t match the accepted definition of unit tests very well” - Kent Beck, TDD by Example
“This is the only use of the phrase “unit test“ in the book, Kent is referring here to his use of the term “unit test” in casual conversation or by implication from xunit tools“ - Ian Cooper
“As you add new test functions to your suite, you add new behavior to your production code. Following the red-green-refactor process you extract that behavior into new functions and classes that do not require independent tests“
- Uncle Bob
Don’t couple tests to structure
I posted on LinkedIn to summarize this problem:
If you write a test for every class, i.e. you’re testing every class in isolation, then your tests are mirroring your code. This structural coupling means if you change anything (e.g. move logic from one class to another, split a class, merge classes, change public interfaces of classes, etc.) then your tests will break
On the other hand, if you write a test for a “module“ (it may span multiple classes), then hyour tests are not mirroring the code. You’re free to refactoring (change the module implementation, e.g. change the design of internal classes, their interfaces), BUT your tests won’t break
When dependencies are just pure logic I use real ones.
When they do some effects like database queries I have parameters for A with potential results of BCD. Then only when B result is missing it is called.
This way I can kind of mock it, without using flaky mocking.
Example code
```
class SchedulePlanner {
public function plan(Datetime date, ?Employee[] employees = null, ?Shift[] = null) {
employees ??= self::getEmployees(date);
shifts ??= self::getShifts(date);
(...)
}
}
```
And then in test I can just pass the results I'd want.
```
planner = new SchedulePlanner(date);
//In real test customize employees and shifts
employees = EmployeeFactory::create(2);
shifts = ShiftFactory::create(2);
plan = planner->plan(date,employees, shifts);
```
If I were to describe it shortly then I'd say it's functional approach bolted onto OOP.
I don't know if it's very good, but for me it has worked so far.
Another way to explain it: looking at last image you'll see that you test only the class accessible from outside the module.
So tests should only do things that production code would do.
Of course you can have tests for internal classes inaccessible from outside. But it's rather a function test or class test, not unit test. You could call it a logic test maybe.