A "Unit" In A Test Is Not The Class Under Test
Autor
Marcus HeldHi,
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