spock mocking stubbing
Verspotten, Stubben und Spionieren mit Spock:
Parametrisiertes Testen im Spock Framework wurde hier ausführlich erklärt Reihe von Trainings-Tutorials zu Spock .
Mocking und Stubbing sind einer der wichtigsten Bausteine umfangreicher Unit-Tests. Die Unterstützung für das Verspotten und Subbing ist wie die Kirsche auf dem Kuchen für einen Rahmen.
Bei vorhandenen Frameworks wie JUnit, JBehave usw. ist die Unterstützung für Mocks und Stubs nicht sofort einsatzbereit. Daher muss ein Entwickler Bibliotheken von Drittanbietern wie Mockito, PowerMock, EasyMock usw. verwenden, um sie im Internet zu verwenden Unit-Tests.
Um Mocks und Stubs und ihre Anwendungsfälle zu verstehen, können Sie sich unsere Serie ansehen Mockito-Tutorial .
In diesem Tutorial erfahren Sie mehr über die integrierten Mocking- und Stubbing-Funktionen, die in die Spock-Bibliothek selbst integriert sind. Dies würde es wiederum ermöglichen, die einfachere Groovy-Syntax zu verwenden und dadurch die Notwendigkeit zu verringern, weitere 3 hinzuzufügen / einzuschließenrdParteibibliotheken.
Sie können jederzeit andere Mocking-Frameworks in Ihre Tests einbeziehen, da der gesamte gültige Java-Code ebenfalls gültiger Groovy-Code ist.
Was du lernen wirst:
- Anwendung im Test
- Spock verspotten
- Stubbing in Spock
- Spock ausspionieren
- Fazit
- Quellcode für die Anwendung
- Literatur-Empfehlungen
Anwendung im Test
Definieren wir zunächst eine Java-Beispielanwendung, die wir mithilfe von Mocks und Stubs im Spock-Framework testen werden.
Wir werden an einer StudentGradeCalculator-App arbeiten, die die Gesamtpunktzahl aus einer abstrahierten Datenbank für einen bestimmten Studentenausweis entnimmt und eine einfache Logik der Notenzuweisung in Abhängigkeit vom Wert der Gesamtpunktzahl aufweist. Wir werden eine Datenbankschnittstelle verwenden, die nur wenige Methoden zum Abrufen und Aktualisieren der Schülerergebnisse und -noten enthält.
Der Code für die Anwendung ist im letzten Abschnitt dieses Tutorials verfügbar.
Spock verspotten
Videoanleitung
Implementieren einer Prioritätswarteschlange in Java
In diesem Abschnitt erfahren Sie, wie Sie Mocks im Spock-Framework instanziieren und initialisieren und wie Sie Interaktionen auf dem Mock validieren, d. H. Die Validierung der Aufrufe der Mocks erfolgte gemäß den Erwartungen der zu testenden Methode.
Mit Mocks müssen Sie nicht viele Einstellungen vornehmen, aber Sie können die Interaktionen überprüfen, die mit den Scheinobjekten aufgetreten sind, die der zu testenden Anwendung bereitgestellt wurden.
Mit Mocks können Sie Dinge tun wie:
- Mit welchen Argumenten wurden die Verspottungen aufgerufen?
- Wie viele Aufrufe usw. wurden insgesamt gezählt?
- Feststellung der Reihenfolge der Verspottungen.
Sehen wir uns ein einfaches Beispiel für den StudentGradeCalculator an, in dem wir das verspottete Datenbankimplementierungsobjekt bereitstellen und die Interaktionen mit dem Mock validieren. Wir werden versuchen, die Verspottungsfunktionen anhand einfacher Beispiele zu verstehen.
Bitte beachten Sie, dass alle Interaktionsvalidierungen gemäß Konvention im 'Dann' -Block erfolgen sollten.
Unten finden Sie den Code für die zu testende Methode (was in der “ wann: ' Block)
public String calculateStudentGrade(String studentId) { String grade; // check if grade is already there in database grade = studentDatabase.getStudentGrade(studentId); if(grade!=null && !grade.isEmpty()) { return grade; } List scoreList = studentDatabase.getStudentScores(studentId); Float totalScore = 0F; if(scoreList !=null) totalScore = scoreList.stream().reduce(0F,(a,b)->a+b); if(totalScore > 90) { grade = 'A'; } else if (totalScore > 80) { grade = 'B'; } else { grade = 'C'; } // update the calculated grade in database studentDatabase.updateStudentGrade(studentId, grade); return grade; }
# 1) Validieren der Interaktionen mit genauen Argumenten: Lassen Sie uns zunächst die Interaktionen mit den genau erwarteten Argumenten validieren. Hier erwarten wir, dass die verspotteten Methoden mit den genauen Argumenten aufgerufen werden (gemäß dem Methodenausführungsablauf).
Hier ' studentDatabase ”Ist der Mock einer Datenbankschnittstelle, für die wir die Interaktionen validieren.
def 'illustrate mocks for interaction verification with arguments'() { when: studentReportGenerator.calculateStudentGrade('123'); then: 1*studentDatabase.updateStudentGrade('123','C') 1*studentDatabase.getStudentGrade('123') }
Wie oben gezeigt, validieren wir mit den genauen Argumenten, so dass die verspottete Implementierung mit aufgerufen worden sein muss. Änderungen an diesen Argumenten führen dazu, dass der Test fehlschlägt und das Fehlerprotokoll den entsprechenden Grund anzeigt.
Versuchen wir, die Note in ' updateStudentGrade 'Bis' A 'anstelle des tatsächlich als' C 'bezeichneten und sehen, welchen Fehler wir erhalten, wenn der Test ausgeführt wird.
Too few invocations for: 1*studentDatabase.updateStudentGrade('123','A') (0 invocations) Unmatched invocations (ordered by similarity): 1 * studentDatabase.updateStudentGrade('123', 'C') 1 * studentDatabase.getStudentScores('123')
Es wird ein Fehler wie 'Zu wenige Aufrufe' angezeigt, da der Mock-Aufruf mit den angegebenen Argumenten nicht gefunden werden kann.
#zwei) Lassen Sie uns nun sehen, wie Sie die Mock-Interaktionen validieren können, ohne die tatsächlichen Argumentwerte anzugeben, d. H. Was uns interessiert, ist nur zu wissen, dass der Mock für die Methode aufgerufen wurde, aber nicht mit welchen Argumenten.
Diese Art von Anforderungen tritt am häufigsten beim Schreiben von Komponententests für den tatsächlichen Produktionscode auf, da es nicht immer einfach ist, die tatsächlichen Argumente zu identifizieren, die im Wesentlichen von der Kerngeschäftslogik der zu testenden Anwendung abhängen.
Die Syntax ist einfach. Sie müssen lediglich einen Unterstrich '_' für ein Argument verwenden, bei dem der tatsächliche Wert nicht bekannt ist.
Zum Beispiel, Um nach einem String-Wert zu suchen, können Sie nur erwähnen “_ Als String ”Anstelle eines Arguments im Test und es sollte für jeden String-Wert übergeben werden (ähnlich für andere primitive sowie benutzerdefinierte Datentypen).
Lassen Sie uns dies anhand eines Beispiels verstehen
def 'illustrate mocks for interaction verification with generic matchers'() { when: studentReportGenerator.calculateStudentGrade('123'); then: 1*studentDatabase.updateStudentGrade(_ as String, _ as String) 1*studentDatabase.getStudentGrade('123') }
Ein wichtiger Punkt hierbei ist, dass Sie immer mischen und abgleichen können, welche Argumente bekannt sind und welche nicht. Im folgenden Beispiel validieren wir beispielsweise die Interaktion eines Modells mit den tatsächlichen Argumenten und des anderen mit den losen Matchern.
#3) Zuletzt sehen wir uns ein Szenario an, in dem wir die Reihenfolge des Scheinaufrufs ermitteln können, d. H. Welche Reihenfolge die Verspottungen aufgerufen wurden, als der Test ausgeführt wurde.
Manchmal ist es wichtig, den Ereignisfluss zu validieren, wenn mehrere Mitarbeiter / Mocks an der zu testenden Anwendung beteiligt sind. Es ist hilfreich zu verstehen und zu validieren, dass die Methoden in einer festgelegten Reihenfolge aufgerufen wurden.
def 'illustrate mocks for validating order'() { when: studentReportGenerator.calculateStudentGrade('123'); then: 1*studentDatabase.getStudentGrade('123') then: 1*studentDatabase.updateStudentGrade(_ as String, _ as String) }
Dies kann erreicht werden, indem einfach mehrere 'then:' - Blöcke in der Reihenfolge der Mock-Sequenzerwartungen verwendet werden. Wenn die angegebene Sequenz nicht der tatsächlichen Reihenfolge des Aufrufs entspricht, wird ein Fehler mit der Angabe 'Falsche Aufrufreihenfolge' ausgegeben.
Zum Beispiel, wenn ich die Reihenfolge der oben genannten ändere dann Anweisungen, die Testausführung wird einen Fehler wie unten gezeigt auslösen.
Wrong invocation order for: 1*studentDatabase.updateStudentGrade(_ as String, _ as String) (1 invocation) Last invocation: studentDatabase.updateStudentGrade('123', 'C')
Stubbing in Spock
Videoanleitung
Wir haben uns mit Mocking befasst und sehen uns nun an, wie Stubs für die verspotteten Objekte definiert werden. Stubbing ist nichts anderes als das Einrichten vordefinierter oder vordefinierter Antworten auf die Mock-Aufrufe, um die verschiedenen Abläufe / Szenarien der zu testenden Anwendung zu testen.
Stellen Sie sich vor, Sie programmieren einen Mock, um einen vordefinierten Wert zurückzugeben, wenn er aufgerufen wurde. Wir werden mit derselben StudentGradeCalculator-App fortfahren und die Datenbankschnittstellenaufrufe stubben, um verschiedene Szenarien zu testen.
Ein Stub ist wie ein Mock, der in gewisser Weise das Verhalten des realen Objekts nachahmt. Sie können es einfach als programmierten Mock bezeichnen.
Stubbing-Syntax
Die Syntax für das Stubben lautet 2 Rechtsverschiebungsoperatoren - d. H. >> ''
Um einen Stub für einen Aufruf festzulegen, können Sie ihn wie folgt definieren:
StubbedObject.StubbedMethod(//argumentList) >> “Stubbed Response”
Lassen Sie uns nun die verschiedenen Stubbing-Szenarien anhand von Beispielen verstehen.
# 1) Stubbing mit aktuellen Parametern: Wenn die Argumente im Voraus bekannt sind oder wenn Sie Stub nur festlegen möchten, wenn der Aufruf mit angegebenen Argumenten erfolgt, kann diese Art der Angabe von Stubs verwendet werden.
def 'illustrate stubs with exact matchers'() { given: studentDatabase.getStudentScores('123') >> (20F, 30F, 50F) when: def grade = studentReportGenerator.calculateStudentGrade('123') then: grade == 'A' }
Hier können Sie sehen, dass der Stub mit einem genauen Argument festgelegt wurde, d. H. StudentId in diesem Fall als '123' (für jeden anderen Wert wird der Stub nicht aufgerufen und es wird eine Standardantwort zurückgegeben).
# 2) Stubbing mit nachsichtigen Matchern: Wenn die Argumente nicht bekannt sind (oder nicht wichtig sind), können wir sie lose erwähnen, wie wir es für Mocks getan haben, und die Syntax bleibt gleich, d. H. Der Unterstrich '_'.
def 'illustrate stubs with loose matchers'() { given: studentDatabase.getStudentScores(_ as String) >> (20F, 30F, 10F) when: def grade = studentReportGenerator.calculateStudentGrade('123') then: grade == 'C' }
#3) Sehen wir uns ein weiteres kurzes Beispiel an, in dem wir einen Stub eingerichtet haben, um eine Ausnahme auszulösen.
Diese Szenarien sind sehr nützlich, um die Fehlerbehandlungslogik einer getesteten Anwendung zu validieren (wie in der realen Welt ist es nicht möglich, alle Ausnahmen zu generieren, aber ein einfacher Stub könnte eingerichtet werden, um die gewünschte Ausnahme zurückzugeben und sie dann zu bestätigen der dann Block).
def 'illustrate stubs with exceptions thrown'() { given: studentDatabase.getStudentScores(_ as String) >> {throw new RuntimeException()} when: studentReportGenerator.calculateStudentGrade('123') then: thrown(RuntimeException.class) }
Spock ausspionieren
Spione basieren auf realen Objekten d.h. sie benötigen die Schnittstellenimplementierung und nicht die abstrakte Schnittstelle selbst. Spione sind mächtig und können es Ihnen ermöglichen, echte Methoden für die zu testende Anwendung aufzurufen und zu überprüfen, für welche Argumente die Methoden aufgerufen wurden.
Spione ermöglichen auch das Definieren von Teilverspottungen für die Instanzen von Spionageobjekten. Angenommen, Sie möchten das Verhalten einiger Methoden für das Objekt definieren, dann können Sie den Rest als echte Methodenaufrufe aufrufen.
Diese sind normalerweise in Situationen nützlich, in denen einige Schnittstellenmethoden möglicherweise nicht implementiert sind und nur wenige andere voll funktionsfähig sind. Daher können Sie als Entwickler die nicht implementierten Methoden stubben und die tatsächlichen Implementierungen der Funktionsmethoden aufrufen.
Es sollte beachtet werden, dass für Spion-Objekte, sofern keine Stubs definiert sind, das Standardverhalten darin besteht, die eigentliche Implementierung aufzurufen. Trotzdem sollten Spione nicht häufig angerufen werden, und die gesamte Szenarioabdeckung kann mithilfe von Mocks und Stubs und einer Kombination aus diesen erreicht werden.
Sehen wir uns einige Beispiele für die Verwendung von Spies im Spock-Framework an StudentGradeCalculator (Wir haben eine echte Implementierung des erstellt StudentDatabase Das ist eine In-Memory-Implementierung mit HashMap um das Aufrufen realer Methoden und das Zurückgeben von Daten zu veranschaulichen. Der Code wird im letzten Abschnitt des Tutorials verfügbar sein):
# 1) Spionage mit einer Kombination aus Stub- und echten Methodenaufrufen
def 'illustrate spies'() { given: StudentDatabase spiedStudentDatabase = Spy(StudentDatabase.class) def studentReportGenerator = new StudentReportGenerator(spiedStudentDatabase) when: def grade = studentReportGenerator.calculateStudentGrade('123') then: grade == 'A' 1*spiedStudentDatabase.getStudentGrade(_ as String) >> 'A' }
Das obige Beispiel zeigt die Syntax zum Erstellen von Spy mithilfe des Spock-Frameworks. Der Stub wird zum Deklarationszeitpunkt selbst definiert.
Außerdem können die ausspionierten Aufrufe wie im then-Block dargestellt überprüft werden (mit losen Argument-Matchern, die für bestimmte Argumente definiert werden können).
# 2) Spionage mit allen echten Methodenaufrufen
def 'illustrate spies with real method call'() { given: StudentDatabase spiedStudentDatabase = Spy(StudentDatabase.class) def studentReportGenerator = new StudentReportGenerator(spiedStudentDatabase) when: def grade = studentReportGenerator.calculateStudentGrade('123') then: grade == 'C' 1*spiedStudentDatabase.getStudentGrade('123') }
Im obigen Beispiel gehen alle Aufrufe an die eigentliche Implementierung, da wir kein Stub-Verhalten erwähnt haben.
Fazit
In diesem Tutorial haben wir alles über die eingebauten Techniken zum Verspotten von Stub und Spy mithilfe des Spock-Frameworks gelernt. Spock macht es einfach, indem diese Funktionen als Teil des Frameworks selbst mit einer besser lesbaren, groovigen Syntax zusammen mit dem geringeren Boilerplate-Code kombiniert werden.
Mocks, Stubs und Spies werden häufig in Unit-Tests verwendet, um die Abdeckung zu erhöhen und die Kerngeschäftslogik der zu testenden Anwendung zu testen oder zu validieren.
Quellcode für die Anwendung
StudentReportGenerator.java - Dies ist die zu testende Methode / Anwendung
package app.studentScores; import java.util.List; public class StudentReportGenerator { public IStudentDatabase studentDatabase; public StudentReportGenerator(IStudentDatabase studentDatabase) { this.studentDatabase = studentDatabase; } public String calculateStudentGrade(String studentId) { String grade; // check if grade is already there in database grade = studentDatabase.getStudentGrade(studentId); if(grade!=null && !grade.isEmpty()) { return grade; } List scoreList = studentDatabase.getStudentScores(studentId); Float totalScore = 0F; if(scoreList !=null) totalScore = scoreList.stream().reduce(0F,(a,b)->a+b); if(totalScore > 90) { grade = 'A'; } else if (totalScore > 80) { grade = 'B'; } else { grade = 'C'; } // update the calculated grade in database studentDatabase.updateStudentGrade(studentId, grade); return grade; } }
IStudentDatabase.java - Datenbankschnittstelle
package app.studentScores; import java.util.List; public interface IStudentDatabase { List getStudentScores(String studentId); void updateStudentGrade(String studentId, String grade); String getStudentGrade(String studentId); }
StudentDatabase.java - InMemory-Implementierung der IStudentDatabase.java-Schnittstelle
package app.studentScores; import java.util.*; public class StudentDatabase implements IStudentDatabase { private Map scoreMap; private Map gradeMap; public StudentDatabase() { this.scoreMap = new HashMap(); this.gradeMap = new HashMap(); scoreMap.put('123', Arrays.asList(40F, 30F, 30F)); scoreMap.put('456', Arrays.asList(10F, 10F, 30F)); gradeMap.put('123', 'C'); gradeMap.put('456', 'A'); } @Override public List getStudentScores(String studentId) { return scoreMap.get(studentId); } @Override public void updateStudentGrade(String studentId, String grade) { gradeMap.put(studentId,grade); } @Override public String getStudentGrade(String studentId) { return gradeMap.get(studentId); } }
In unserem nächsten Tutorial erfahren Sie, wie Sie das Spock-Framework in andere Test-Frameworks und -Technologien integrieren können.
PREV Tutorial | NÄCHSTES Tutorial
Literatur-Empfehlungen
- Unit-Tests mit Spock Framework schreiben
- Spock Interview Fragen mit Antworten (am beliebtesten)
- Spock für Integration und Funktionstests mit Selen
- Datengesteuertes oder parametrisiertes Testen mit Spock Framework
- Spock Tutorial: Testen mit Spock und Groovy
- Beste KOSTENLOSE C # Tutorial-Serie: Der ultimative C # Leitfaden für Anfänger
- Lasttests mit HP LoadRunner-Tutorials
- Datums- und Uhrzeitfunktionen in C ++ mit Beispielen