A "Unit" In A Test Is Not The Class Under Test
Find the English version below
Hi ,
Was ist eigentlich eine "Unit" im Unit-Test?
Dumme Frage, sagst du dir vielleicht.
Aber sie ist interessant. Über viele Jahre war ich, wie die meisten die über das Thema sprechen, der Meinung:
Eine Unit ist eine Klasse, die ich testen möchte.
Und vielleicht hast du das so auch gesehen. Und du hast mit Sicherheit damit ein paar Probleme gehabt.
Zum Beispiel, die Symptome des daraus resultierendem Coupling
Denn was passiert, wenn wir eine UserServiceTest
Klasse schreiben? Wir haben - ohne es zu merken - ein semantisches Coupling zwischen der UserService
Klasse und dem UserServiceTest
hergestellt.
Wenn wir den UserService
umbenennen, dann müssen wir auch den UserServiceTest
umbenennen. Wie oft ich das schon vergessen habe.
Noch vor drei Jahren schrieb ich den Blog "Best Practices For Unit Tests".
In diesem empfahl ich in Java die Methodensignatur in der Form methodName_conditions_expectedResult
zu schreiben.
Gleiches Problem. Eine semantische Kopplung zwischen dem Test und meiner Implementierung. Eine Umbenennung der Methode sorgt für eine Umbenennung der Testfunktion. Oder - wahrscheinlicher - für Verwirrung, weil ich es vergesse.
Im gleichen Blog beschrieb ich noch ein anderes Muster, dass ich heute als Anti-Pattern empfinde.
Ich empfehle (fast) alles zu mocken.
Doch, wenn wir alles mocken, dann spiegeln wir nur die Implementierung - mit etwas anderen Worten.
Im ersten Satz dieses Absatz spreche ich aus, was heute die meisten Entwickler leben:
A unit test - by definition - tests only, and exclusively, the unit under test.
Aber genau hier liegt das Problem. Die Definition ist falsch.
In der ursprünglichen Definition war mit "Unit" die Unabhängigkeit des Tests selbst gemeint.
Es war ein weitverbreitetes Problem, dass Tests aufeinander aufbauten. Dieser Begriff sollte klar machen, dass ein Unit-Test unabhängig lauffähig ist.
Alle Probleme, die ich beschrieben habe, sorgen für eine starke Kopplung zwischen den Implementierungsdetails und dem Test. Doch dabei wollen wir gar nicht die Implementierung testen.
Wir wollen ein Verhalten testen.
Und darauf musst du dich konzentrieren. Statt isEmpty_emptyList_true
möchtest du einen listWithNoElementsIsEmpty
Test.
Hast du gemerkt, wie sich das Mindset verändert? Ich habe das Verhalten einer Liste beschrieben. Es wird nicht mehr die API getestet.
Wenn wir uns von der Implementierung trennen, dann wird alles einfacher.
Tests sagen uns, welches Verhalten nicht mehr funktioniert. Ein Refactoring sorgt nicht mehr automatisch zu vielen false positives. Wir schreiben weniger Tests. Decken aber das gleiche ab.
Probiere das doch für dein nächstes Feature aus 🙂
Rule the Backend,
~ Marcus
Hi ,
What is actually a "Unit" in a Unit Test?
Dumb question, you might say.
But it's an interesting one. For many years, like most people who talk about this topic, I believed:
A unit is a class that I want to test.
And maybe you saw it that way too. And you certainly had a few problems with it.
For example, the symptoms of resulting Coupling
What happens when we write a UserServiceTest
class? We have - without realizing it - established a semantic coupling between the UserService
class and the UserServiceTest
.
If we rename the UserService
, then we also have to rename the UserServiceTest
. How often I have forgotten that.
Just three years ago, I wrote the blog "Best Practices For Unit Tests".
In it, I recommended writing the method signature in Java in the form methodName_conditions_expectedResult
.
Same problem. A semantic coupling between the test and my implementation. Renaming the method leads to renaming the test function. Or - more likely - to confusion because I forget it.
In the same blog, I described another pattern that I now consider an anti-pattern.
I recommend (almost) mocking everything.
But, if we mock everything, then we only reflect the implementation - in slightly different words.
In the first sentence of this paragraph, I express what most developers live today:
A unit test - by definition - tests only, and exclusively, the unit under test.
But this is exactly where the problem lies. The definition is wrong.
In the original definition, "Unit" meant the independence of the test itself.
It was a widespread problem that tests were built on each other. This term was meant to clarify that a unit test is independently runnable.
All the problems I have described cause a strong coupling between the implementation details and the test. But we don't want to test the implementation.
We want to test a behavior.
And that's what you need to focus on. Instead of isEmpty_emptyList_true
, you want a listWithNoElementsIsEmpty
test.
Do you notice how the mindset changes? I described the behavior of a list. It's no longer about testing the API.
When we detach from the implementation, everything becomes easier.
Tests tell us which behavior no longer works. Refactoring no longer automatically leads to many false positives. We write fewer tests. But cover the same.
Try this out for your next feature 🙂
Rule the Backend,
~ Marcus