22.6 Dummy, Fake, Stub und Mock
Gute objektorientiert entworfene Systeme zeichnen sich dadurch aus, dass es eine hohe Interaktion mit anderen Objekten gibt. Idealerweise zerlegt eine Klasse ein Problem nur bis zu dem Punkt, an dem es sich einer anderen Klasse bedienen kann, die dieses einfachere Problem löst. Schwierig wird es, wenn eine eigene Klasse auf eine andere komplexe Klasse zurückgreift und das Objekt nur dann sinnvoll arbeitet, wenn das referenzierte Objekt da ist und irgendwie sinnvoll antwortet. Diese Abhängigkeit ist ungünstig, denn das Ziel eines guten Tests besteht ja darin, lokal zu sein, also die eigentliche Klasse zu testen und nicht alle referenzierten Klassen um sie herum gleich mit.
In der Praxis begegnen uns drei Hilfskonstrukte, die die Lokalität von Tests ermöglichen:
Fake-Objekte: Sie sind eine gültige Implementierung einer Schnittstelle. Wenn zum Beispiel ein Repository auf die Datenbank geht, kann eine Fake-Implementierung Datensätze in einer Datenstruktur speichern. Das Verhalten ist nachgebildet und vereinfacht, aber funktionsfähig. So liefert ein Fake-Repository statt Kunden aus der Datenbank immer die gleichen n vorgefertigten Kunden. Fake-Objekte sind auch praktisch, wenn zum Beispiel eine GUI-Anwendung programmiert wird, die statt echter Datenbankdaten erst einmal mit den Fake-Objekten entwickelt wird und so die Demodaten anzeigt. Wenn ein Team die GUI baut und ein anderes Team den Service, so können beide Gruppen unabhängig arbeiten, und das GUI-Team muss nicht erst auf die Implementierung warten.
Stub-Objekte: Stub-Objekte implementieren ein bestimmtes Protokoll, sodass sie für den Testfall immer die gleichen Antworten geben können. Wenn etwa ein E-Mail-Service eine Methode isTransmitted() anbietet, so kann der Stub immer true liefern. Stubs haben also kein Verhalten, sondern der Rumpf der Methoden ist quasi leer und minimal. Sie gibt es nur für die Testfälle.
Mock-Objekte: Mock-Objekte werden von einem Testfall »aufgeladen« und zeigen dann das gewünschte Verhalten – sie liefern also nicht wie Stubs immer das gleiche Ergebnis. In der Regel werden Mock-Objekte durch Bibliotheken wie mockito (http://mockito.org) oder EasyMock (http://easymock.org) automatisch zur Laufzeit erzeugt.
Diese drei Typen können wir unter dem Oberbegriff Dummy-Objekt zusammenfassen. Grundsätzlich gilt bei den vier Begriffen aber, dass sie von Autoren nicht einheitlich verwendet werden.[ 283 ](Die Seite http://xunitpatterns.com/Mocks,%20Fakes,%20Stubs%20and%20Dummies.html stellt einige Autoren mit ihrer Begriffsnutzung vor. )
[zB] Mockito-Beispiel
Nehmen wir an, alles aus org.mockito.Mockito.* ist statisch importiert und wir wollen eine java.util.List aufbauen. Dazu muss Mockito erst etwas aufbauen, was sich wie List verhält:
List<?> mockedList = mock( List.class );
Im nächsten Schritt muss das Verhalten der speziellen Liste bestimmt werden:
when( mockedList.get(0) ).thenReturn( "tutego" );
Anschließend ist die Liste bereit zur Nutzung:
System.out.println( mockedList.get(0) ); // tutego
Um die Methode mock(…) nicht von Hand aufrufen zu müssen, kann ein JUnit-5-Testfall mit @ExtendWith annotiert werden – dann lassen sich automatisch alle mit @Mock versehenen Attribute initialisieren:
@ExtendWith( MockitoExtension.class )
class MyTest {
@Mock private MyType mock;
@BeforeEach private void setUp() {
when( mock.… ).thenReturn( … );
}
}