Testing: Mocks, Stubs usw.

JennyB

Grünschnabel
Hallo,

bei Testen von Java Anwendungen ist es möglich Test-Duplikate zu verwenden.
Ich habe folgende 4 gefunden:
Dummy, Stub, Fake, Mock!

Jetzt ist mir nicht so ganz klar wann ich welches Objekt einsetze. Kann mir da jemand mal ein Beispiel geben?
Zusätzliche versuche ich die mir erstmal irgendwie zu definieren. Da habe ich jetzt folgendes.

Dummy:
- es ist eine triviale, aber keine echte Implementierung
- meistens werden vordefinierte Werte zurück gegeben
- ersetzt komplexe Abläufe oder Berechnungen

Stub:
- das Verhalten im Code wird simuliert
- Code ist eventuell schon entwickelt, aber zu komplett

Fake:
- implementiert dasselbe Interface, wie das eigentliche Objekt, gibt aber vordefinierte Anwtorten zurück

Mock:
- benötigtes Verhalten für die Rückgabewerte wird festgelegt
- Mock Objekt kann auf Eingaben reagieren und diese verarbeiten
- es sind nur für den Testfall benötigte Funktionen enthalten

Ist das so richtig? Klar ist mir damit die Verwendung aber nicht...

Ich hoffe ihr könnt mir helfen!
 
Hi,

also den Namen Fake habe ich in diesem Zusammenhang noch nie Gehört aber die Unterschiede zwischen den restlichen Test-Duplikaten sind nur sehr schwammig wenn überhaupt definiert. Ich hab zumindest noch nirgends eine explizite Erklärung gefunden. Im Grunde genommen sind es einfach nur unterschiedliche Namen für die gleiche Sache. Ich glaube jeder hat einfach ein Lieblingswort das er dafür benutzt. Meins ist Mock :).

Zitat "Basiswissen Softwaretest von Andreas Spillner & Tilo Linz"
Im Gegensatz zum Stub mit seiner rudimentären Funktionalität bietet ein Dummy einen nahezu vollwertigen Ersatz für die echte Implementierung. Ein Mock unterscheidet sich vom Dummy durch zusätzliche Funktionalität für Testzwecke.
"Link, J.; Fröhlich, P.: Unit Tests mit Java : Der Test-First Ansatz"

Wahrscheinlich kann man das so definieren, wobei ich es so empfinde als ob jeder grade das Wort nimmt auf das er grade Lust hat.

Bei allen drei Objekten geht es eigentlich nur Darum Komplexe Klassen zu ersetzen. Wenn du einen einen Unit Test machst willst du ja wie der Name schon sagt nur die Einheit testen, also nur eine Klasse. Da die Meisten Klassen allerdings auf weitere selbst implementierte Klassen zugreifen müssen diese ersetzt werden. Selbst wenn sie selbst schon getestet und als richtig empfunden wurden. Testet man nämlich mehrere Komponennten ist dies ein Integrationstest der Fehler in der Kommunikation zwischen den Komponennten aufdeckt. Um also die einzelne Einheit zu Testen mus für diese eine Ersatzklasse geschrieben werden. Mocks, Stubs oder auch Dummys ersetzen ein Objekt und bieten dem eigentlichen Testobjekt den Grad an Funktionalität der nötig ist um dieses Objekt zu testen. Diese Testobjekte geben in den Meisten Fällen fest reingecodete Werte Zurück.

Gruß Benny
 
Relevant ist eigentlich nur die Unterscheidung zwischen Mock und Stub. Fake und Dummy würde ich zu Stub zuordnen. Der Kernunterschied ist folgender: bei einem Stub implementiertst du eine Subklasse (oder halt ein Interface direkt) und gibst entweder feste Werte zurück oder implementierst vereinfachtes verhalten. Beispiel wäre eine InMemory Implementierung eines DAOs. Eine Produktivimplementierung würde per JDBC mit einer Datenbank kommunizieren, InMemory hält die Entities einfach in einer Map.

Die Stubmplementierung liegt also auf der "serverseite", d.h. du veränderst das verhalten da, wo es normalerweise auch implementiert wird. Im Testcase benutzt du die Implementierung nur noch.

Mocks sind genau das Gegenteil. Die "programmiert" bzw. erzeugt der Testcase selbst über ein API, dass die Mockbibliohek deiner Wahl anbietet. Hier ein Beispiel mit EasyMock:
Java:
DeinInterface mock = EasyMock.createMock(DeinInterface.class);
EasyMock.expect(mock.eineMethode()).andReturn(5).anyTimes();
EasyMock.replay(mock);

testInstanz.setDependecy(mock);

// Testaufrufe

EasyMock.verify(mock);
Hier wird also ein Mock erzeugt, dass das Interface DeinInterface implementiert. Danach drückst du aus, dass du willst, dass Aufrufe an eineMethode() von dem Mock immer mit 5 beantwortet werden sollen. Dann "spulst" du das Mock zurück und benutzt es. D.h. du lässt deine Klasse, die du testen willst mit dem Mock arbeiten. Das verify am Schluss ist eigentlich nur nötig, wenn du sehr detailliert überprüfen willst, wie oft eine Methode gerufen wird usw.

Beide Strategien haben Vor- und Nachteile. Stubs sind relativ starr, es ist schwierig unterschiedliches Verhalten einer Methode zu implementieren (einmal normaler Rückgabewert, einmal Exception werfen). Benötigt man allerdings sehr oft eine bestimmte Basisimplementierung, dann können Stubs der richtige Weg sein.

Mocks sind natürlcih wesentlich flexibler, bedürfen allerdings auch einiges an Aufwand. So müssen halt erwartete Methodenaufrufe ausspezifiziert werden. Desweiteren ist das dann natürlich kein Glasboxtest mehr, da man die Interna der zu testenden Klasse kennen muss (welche Methoden werden auf der Dependency gerufen).

Joaa... alle Klarheiten beseitigt? ;)

Wichtiges Paper dazu: http://martinfowler.com/articles/mocksArentStubs.html

Gruß
Ollie
 
Erstmal danke für die Antworten! Die haben mir auf jeden Fall schon weiter geholfen!

Also noch mal zusammenfassend:
Bei einem Stub schreibe ich im echten Quellcode rum und ein mit meinem Mock kann ich Objekte erzeugen, die das Verhalten des echten Quellcode nachahmen.


Noch eine Frage:
Wenn ich sage, dass das Mockobjekt z.B. 5 zurück geben soll, wie kann es dann auf Eingaben reagieren?

LG Jenny
 
Das kommt auf die Library an. Bei EasyMock ist das so. In expect(..) schreibst du genau das auf, was du erwartest. Du kannst z.B.

Java:
EasyMock.expect(mock.getUser(user.getId())).andReturn(user);

machen. Dabei ist user ein Teil deines testcases und du möchstest, das das Mock ihn zurückgibt, wenn es mit seiner id gerufen wird. Ganz flexibel wird das ganze über sogenannte ArgumentMatcher. Die reichst du als Parameter in die Methode rein. Die werden dann gerufen, wenn die Methode wirklich gerufen wird:

Java:
EasyMock.expect(mock.getUser(new IArgumentMatcher<Integer>() {

  public boolean match(Integer i) {
    return 5 > i;
  }
}).andReturn(user);

Diese Anweisung sorgt dafür, dass das Mock den user zurück gibt, wenn der hereingereichte Parameter kleiner als 5 ist. Das ist jetzt nur aus dem Kopf, spricht Interfacenamen und Methodennamen weichen zu 99% ab, allerdings ist das Vorgehen genau so. Das spezielle Argument matching braucht man allerdings sehr selten. Die Basics reichen im Allgemeinen.

http://www.easymock.org/

Gruß
Ollie
 
Ok das mit dem Mock habe ich jetzt einigermaßen verstanden!

Jetzt habe ich aber noch eine Frage zu Stubs. Du hast geschrieben
es ist schwierig unterschiedliches Verhalten einer Methode zu implementieren (einmal normaler Rückgabewert, einmal Exception werfen)
Wieso ist das schwierig? Also kann ich damit einen Rückgabewert und eine Exception angeben? Kannst du eventuell einen kleinen Beispielcode geben? Ich kann mir das irgendwie schlecht vorstellen...
 
Okay, angenommen, du hast eine Klasse die du testen möchtest: UserService. Diese benutzt in Produktion eine Implementierung des Interfaces UserDao, dass du zu Testzwecken gegen ein Mock / Stub austauschen willst. Angenommen du hast auf UserDao folgende Methode:
Java:
public User getUser(Long id) throws UserNotFoundException;

Wie willst du jetzt mit einem Stub folgende zwei verschiedene Testfälle abdecken ohne viel Implementierungsaufwand zu haben:

a) Valide id, korrekter User soll zurückgegeben werden, der Service arbeitet damit
b) Invalide id, DAO wirft eine Exception, UserService bahndelt diese korrekt

Mit einem Mock machst du einfach in der einen Testmethode
Java:
EasyMock.expect(daoMock.getUser(user.getId)).andReturn(user);
in der anderen
Java:
EasyMock.expect(daoMock.getUser((Long) EasyMock.anyObject())).andThrow(new UserNotFoundException());

D.h. du spezifizierst das erwartete Verhalten in der Testmethode bzw. in der Testklasse. Bei eine Stub ist das so eine Sache, da diese dann ja als Klasse im Projekt liegen und die Implementierung, die vielleicht für deinen Testcase Sinn macht, in einem anderen Kontext aber dumm ist. Was machst du dann in diesem Kontext?

Stubs eignen sich vor allem da, wo man den Stub von aussen konfigurierbar machen kann, d.h. einfache Datencontainer. Allerdings ist grad das Exceptionwerfen ein Thema, bei dem Stubs eher schlecht aussehen. Klar könntest du in dem Beispiel eben den Stub so implementieren, dass er bei bestimmte ids den User zurückgibt, bei bestimmten anderen dann die Exception wirft. Allerdings ist dann der Code im Test recht unsprechend und wehe, es fasst jemand die Implementierung an ;). Dann brechen ganz schnell Tests, die sich auf diese Implementierung verlassen haben.

Gruß
Ollie
 
Hi
das ist recht interessant für mich, dass es so viele Bezeichnungen gibt :)
Früher in der "guten alten" prozeduralen Programmeirung waren die gängigen Bezeichnungen Dummy, wenn es sich um eine Routine handelte, die für einen Aufrufer geschrieben wurde, um diesen zu testen (also Test von unten). Driver oder Treiber hingegen nannte man aufrufende Testroutinen, die eine aufzurufende Routine testen sollten (also Test von oben).
Gesehen an dieser älteren Definition was wäre da auf Stub und Mock bezogen Dummy bzw Treiber?

mit wissbegierigen Grüßen

Takidoso
 
Treiber:
Werden heute auch noch als Treiber / Testtreiber Bezeichnet.

Dummy:
Alle der hier verwendeten Objekte-> Mock, Stub,.. entsprechen
dem von dir beschriebenen Dummy.

Benny
 
Zurück