TDD und Mocks - best practice

Antoras

Top Contributor
Hallo,

ich bräuchte mal euren Rat in Bezug auf das Schreiben von möglichst eleganten Tests, die mit Mock-Objekten arbeiten.

Bisher gehe ich so vor:
  1. Tests (mit Mocks) auf Basis von Schnittstellen schreiben
  2. Testen
  3. Implementierungen der Schnittstellen schreiben
  4. Mock-Code in den Tests löschen/auskommentieren
  5. Nochmal testen

Mir ist das ständige Refactoring des Mock-Codes aber zu aufwendig, lässt sich das irgendwie vermeiden?

Durch DI kann ich zwar erreichen, dass es dem Test egal ist ob er Mocks oder eine Implementierung bekommt, sobald innerhalb des Tests aber mit den Abhängigkeiten kommuniziert werden muss muss ich ja irgendwo das Verhalten der Mocks definieren. Wenn ich das aus dem Test auslagere (und es z.B. zu den Abhängigkeiten setze) hab ich das Problem, dass ich bei etwaigen Änderungen am Test gleich an mehreren Orten den Code editieren muss.
Im Grunde beschränkt sich das Refactoring "nur" auf das Löschen/Auskommentieren des Mock-Verhaltens, bei vielen Tests ist es aber doch nervig.

Wie geht ihr da vor, damit am Schluss möglichst wenig Refactoring benötigt wird?
 

schalentier

Gesperrter Benutzer
Ich versteh nicht, wieso du Mocks (Mock Code) loeschen willst... ?? Mocks sind dazu gedacht, Abhaengigkeiten zu komplexen Klassen "wegzumocken". Das aendert sich nicht, wenn sich irgendwelche Implementierungen aendern. Oder ich versteh grad was falsch.
 
M

maki

Gast
Ich verstehe nicht was du unter Punkt 1 und Punkt 4 verstehst.

Mocks werden nicht "gelöscht" wenn man mit dem test fertig ist, mocks bleben, weil sonst das SUT (Subject under test, also die zu testende klasse/Einheit) nicht mehr isoliert ist. Kann aber verstehen dass dir das löschen von mocks seltsam vorkommt ;)

Ich mache das ungefähr so:
1. Schreibe einen einfachen test für eine Klasse (ja, kein Interface) die es noch nicht gibt
2. Schreibe den Code den du testen willst, sorge dafür das dein test durchläuft
3. Wenn du merkst, dass dein SUT nun eine Abhängigkeit ("collaborator") haben soll/wird (man will ja SoC einhalten), schreibe ein minimales Interface, und definiere ein mock inkl. verhalten & expectations das dafür sorgt dass dein SUT getestet werden kann (injiziere das mock in das SUT). Das wird übrigens "interface discovery" genannt und ist einer der größten stärken von TDD, damit hilft TDD das Design zu verbessern bzw. zu "entdecken".
4. Wenn der test durchläuft, refactoring der/des tests und des SUT, dann wieder Punkt 1

Irgendwann wirst du eine Implementierung für das collaborater interface schreiben, bzw. zuerst den test, dann den Code ;)
Das läuft auch nach diesem Schema, allerdings ist das SUT nun eine Implementierung des "ehemaligen" collaborator.

Man testet niemals mocks, macht gar keinen Sinn, mocks die prüfen ob sie richtig aufgerufen wurden (expectations) sparen einem übrigens viele asserts, kann manchmal sogar tests ganz ohne asserts schreiben, das mock prüft ja schon die expectations.

Es gibt gute Literatur über TDD, zB. das Buch von Beck, aber auch sehr viele gute Internet Artikel, zu Mocks gibt es auch sehr viel, empfehle das den Artikel "Mock Roles, not objects", zu finden über Google.

Ach ja: Räume deine tests regelmässig auf, nutze factory methoden wo es nur geht, vermeide unbedingt Redundanz. Wenn man zB. einen Konstruktor ändert und 4 Testklassen können nicht mehr kompiliert werden, mache deine Änderung rückgängig, und sorge dafür das der Konstruktor in den tests nur noch von einer einzigen Factory Methode direkt verwendet wird.
 
Zuletzt bearbeitet von einem Moderator:

Antoras

Top Contributor
Mocks sind dazu gedacht, Abhaengigkeiten zu komplexen Klassen "wegzumocken". Das aendert sich nicht, wenn sich irgendwelche Implementierungen aendern.
Mocks werden nicht "gelöscht" wenn man mit dem test fertig ist, mocks bleben, weil sonst das SUT (Subject under test, also die zu testende klasse/Einheit) nicht mehr isoliert ist.
Und was passiert wenn die Abhängigkeiten irgendwann implementiert werden? Ich muss ja irgendwann gucken ob die einzelnen SUTs auch miteinander arbeiten können und nicht nur mit den Mocks. Dafür bräuchte ich ja dann zusätzliche Tests, wenn die bestehenden unangetastet bleiben.
kennst du mockito?
Ja, ich hab damit gemeint was passieren soll wenn ein Test geschrieben wird, der nicht weiß ob er mit Mocks oder mit konkreten Implementierungen arbeitet.
 

schalentier

Gesperrter Benutzer
Und was passiert wenn die Abhängigkeiten irgendwann implementiert werden? Ich muss ja irgendwann gucken ob die einzelnen SUTs auch miteinander arbeiten können und nicht nur mit den Mocks. Dafür bräuchte ich ja dann zusätzliche Tests, wenn die bestehenden unangetastet bleiben.

Exakt, diese zusaetzlichen Tests heissen dann Integrationstests. Die anderen (die die Mocks benutzen) sind normalerweise Unittests, d.h. sie testen nur und ausschliesslich die "Unit" (normalerweise die Klasse).
 
M

maki

Gast
Und was passiert wenn die Abhängigkeiten irgendwann implementiert werden?
Nix.

Ich muss ja irgendwann gucken ob die einzelnen SUTs auch miteinander arbeiten können und nicht nur mit den Mocks.
Das sind keine isolierten Unittests mehr.

Dafür bräuchte ich ja dann zusätzliche Tests, wenn die bestehenden unangetastet bleiben.
Richtig, meist wird das dann gleich in integrationstests/funktionalen tests gemacht.
 

Antoras

Top Contributor
Ok, dann wird das ein bisschen klarer. Könnt ihr mir gute Bücher/Artikel empfehlen, die einen Einstieg in das Thema Integrationstests bieten?
 
M

maki

Gast
"XUnit Test Patterns - Refactoring Test Code" von Gerard Meszaros
Darin geht es um alle möglichen Arten von Tests.
 

Ähnliche Java Themen

Neue Themen


Oben