Verwenden von Mockito zum Stubben und Ausführen von Methoden zum Testen

Lesezeit: 8 Minuten

Ich habe kürzlich ein paar jUnit- und Mockito-orientierte Fragen gestellt, und ich habe immer noch große Probleme, den Dreh raus zu bekommen. Die Tutorials sind alle für sehr einfache Beispiele, daher habe ich Schwierigkeiten, meine Testfälle zu skalieren, damit sie für meine Klassen funktionieren.

Ich versuche gerade, einige Testfälle für eine Methode zu schreiben, die ich in einem meiner Agenten in einer Webapp habe. Die Methode interagiert mit einigen anderen Methoden innerhalb des Agenten, um einige Objekte zu validieren. Ich möchte jetzt nur diese eine Methode testen.

Folgendes habe ich versucht:

  1. Erstellen Sie ein Mockito-Objekt meines Agenten wie folgt:

    MyProcessingAgent mockMyAgent = Mockito.mock(MyProcessingAgent.class);

  2. Richten Sie Stubs (hoffentlich der richtige Begriff) mit Mockito ein.

    Mockito.when(mockMyAgent.otherMethod(Mockito.any(arg1)).thenReturn(requiredReturnArg);

  3. Versuchen Sie, meine Methode so auszuführen:

    List myReturnValue = mockMyAgent.methodThatNeedsTestCase();

Ich hatte mit Dingen gerechnet myReturnValue, aber stattdessen 0 erhalten, also habe ich versucht zu debuggen. Wenn ich die Methode aufrufe, wird sie nie ausgeführt. Ich habe einen Debug-Punkt in der ersten Zeile der Methode, der nie berührt wird.

Wenn ich den Code in einer Methode einer Klasse ausführen möchte, aber andere Methoden in der Klasse (eine, die versucht, mit Datenbanken in der Außenwelt zu interagieren) dazu zwingen, gefälschte Werte zurückzugeben. Geht das mit Mockito?

Es scheint, dass meine derzeitige Herangehensweise kein korrekter Teststil ist, aber ich bin mir nicht sicher, wie ich weiter vorgehen soll. Kann ich meine Klasse verspotten und eine Methode wie gewohnt ausführen lassen, während andere Methoden gestubbt werden, um meine angegebenen Werte zurückzugeben, sodass ich mich beim Testen dieser einen Methode nicht mit dem Datenzugriff befassen muss?

Benutzeravatar von Boris the Spider
Boris die Spinne

Du verwechselst a Mock mit einer Spy.

In einem Spott alle Methoden werden gestubbt und geben “intelligente Rückgabetypen” zurück. Dies bedeutet, dass das Aufrufen einer beliebigen Methode in einer simulierten Klasse ausgeführt wird nichts tun es sei denn, Sie geben Verhalten an.

In einem Spy ist die ursprüngliche Funktionalität der Klasse immer noch vorhanden, aber Sie können Methodenaufrufe in einem Spy validieren und auch das Methodenverhalten überschreiben.

Was Sie wollen, ist

MyProcessingAgent mockMyAgent = Mockito.spy(MyProcessingAgent.class);

Ein kurzes Beispiel:

static class TestClass {

    public String getThing() {
        return "Thing";
    }

    public String getOtherThing() {
        return getThing();
    }
}

public static void main(String[] args) {
    final TestClass testClass = Mockito.spy(new TestClass());
    Mockito.when(testClass.getThing()).thenReturn("Some Other thing");
    System.out.println(testClass.getOtherThing());
}

Ausgabe ist:

Some Other thing

NB: Sie sollten wirklich versuchen, die Abhängigkeiten für die zu testende Klasse zu simulieren nicht die Klasse selbst.

  • Bitte lesen Sie meine Aussage zu Spionen und lassen Sie es mich wissen, wenn Sie anderer Meinung sind.

    – Johannes B

    12. April 2013 um 16:15 Uhr

  • @ JohnB Ich denke, Sie haben Recht. Ich habe durch Antwort erweitert. Ich wusste nicht, dass das OP die Methode intern aufruft.

    – Boris die Spinne

    12. April 2013 um 16:16 Uhr

  • Danke für die ausführlichen Beschreibungen, das ist sehr hilfreich. Was beide Antworten empfehlen (zur Verdeutlichung), ist, dass ich mit Mock die falsche Klasse angreife. Stattdessen sollte ich alle Datenzugriffsobjekte verspotten, die meine Klasse benötigt, und dann eine normale Instanz meiner Klasse zum Testen verwenden?

    – Kyle

    12. April 2013 um 16:20 Uhr

  • Also lag ich falsch. Zumindest auf meiner Box fängt ein Spion interne Anrufe ab. Ich habe ein Szenario eingerichtet, in dem m1() m2() aufruft. Ich spioniere das Objekt aus und stub m2(). In meinem Test rufe ich m1() auf. m2() im Objekt wird nie erreicht, der Stub wird aufgerufen. IMHO ist das immer noch schlechte Praxis, aber gut zu wissen.

    – Johannes B

    12. April 2013 um 16:22 Uhr

  • @JohnB ja, anscheinend haben wir die Waffe ein wenig übersprungen. spy funktioniert gut. Offensichtlich nicht der richtige Weg Ja wirklich aber einige schlechte Unit-Tests sind wahrscheinlich besser als gar keine …

    – Boris die Spinne

    12. April 2013 um 16:25 Uhr

Benutzeravatar von John B
Johannes B

Die Idee, die zu testende Klasse zu verspotten, ist also ein Gräuel für die Testpraxis. Sie sollten dies NICHT tun. Da Sie dies getan haben, tritt Ihr Test in Mockitos Spottklassen ein, nicht in Ihre zu testende Klasse.

Das Spionieren wird auch nicht funktionieren, da dies nur einen Wrapper / Proxy um die ausspionierte Klasse bereitstellt. Sobald die Ausführung innerhalb der Klasse ist, wird sie nicht durch den Proxy gehen und daher den Spion nicht treffen. UPDATE: Obwohl ich glaube, dass dies für Spring-Proxys zutrifft, scheint es für Mockito-Spione nicht zuzutreffen. Ich habe ein Szenario eingerichtet, in dem Methode m1() Anrufe m2(). Ich spioniere das Objekt aus und stub m2() zu doNothing. Wenn ich rufe m1() In meinem Test, m2() der Klasse nicht erreicht. Mockito ruft den Stub auf. Es ist also möglich, einen Spion einzusetzen, um das zu erreichen, was verlangt wird. Ich möchte jedoch wiederholen, dass ich es als schlechte Praxis betrachten würde (IMHO).

Sie sollten alle Klassen verspotten, von denen die zu testende Klasse abhängt. Dadurch können Sie das Verhalten der Methoden steuern, die von der zu testenden Methode aufgerufen werden, indem Sie die Klasse steuern, die diese Methoden aufrufen.

Wenn Ihre Klasse Instanzen anderer Klassen erstellt, sollten Sie die Verwendung von Fabriken in Betracht ziehen.

  • +1 für die Verwendung eines Wortes wie Anathema scheint hart zu sein, aber ich glaube, ich verstehe Ihren Standpunkt. Ich versuche immer noch, mich um die Tests zu kümmern, ich glaube, ich bin einfach aus dem völlig falschen Blickwinkel darauf gekommen.

    – Kyle

    12. April 2013 um 16:21 Uhr

  • wahrscheinlich wahr für ein A-Wort. Die Tatsache, dass Sie sogar Unit-Tests durchführen, ist ein gutes Zeichen. Weiter so. Prost!

    – Johannes B

    12. April 2013 um 16:26 Uhr

andybs Benutzeravatar
andyb

Du hast es fast geschafft. Das Problem ist, dass die Klasse im Test (SCHNEIDEN) ist nicht für Unit-Tests gebaut – das ist es nicht Ja wirklich gewesen TDD‘d.

Stellen Sie sich das so vor…

  • Ich muss eine Funktion einer Klasse testen – nennen wir es mal meineFunktion
  • Diese Funktion ruft eine Funktion in einer anderen Klasse/einem anderen Dienst/einer anderen Datenbank auf
  • Diese Funktion ruft auch eine andere Methode auf der auf SCHNEIDEN

Im Unittest

  • Sollte eine konkrete erstellen SCHNEIDEN oder @Spy darauf
  • Du kannst @Mock alle anderen Klassen/Dienste/Datenbanken (dh externe Abhängigkeiten)
  • Du könnte Stub die andere Funktion, die in aufgerufen wird SCHNEIDEN aber es ist nicht wirklich wie Einheit Tests sollten durchgeführt werden

Um zu vermeiden, dass Code ausgeführt wird, den Sie nicht streng testen, können Sie diesen Code in etwas abstrahieren, das möglich ist @Mocked.

In diesem sehr Einfaches Beispiel: Eine Funktion, die ein Objekt erstellt, ist schwer zu testen

public void doSomethingCool(String foo) {
    MyObject obj = new MyObject(foo);

    // can't do much with obj in a unit test unless it is returned
}

Aber eine Funktion, die einen Dienst verwendet, um MyObject abzurufen, ist einfach zu testen, da wir den schwierig/unmöglich zu testenden Code in etwas abstrahiert haben, das diese Methode testbar macht.

public void doSomethingCool(String foo) {
    MyObject obj = MyObjectService.getMeAnObject(foo);
}

as MyObjectService kann verspottet und auch verifiziert werden, dass .getMeAnObject() mit aufgerufen wird foo Variable.

  • wenn meineFunktion und meineFunktion2 sind Teil der SCHNEIDENund meineFunktion Anrufe meineFunktion2 Wie in Ihrem Beispiel angegeben, wäre Spionage-basiertes teilweises Verspotten nicht die einzige Option? sollte meineFunktion2 aus dem verschoben werden SCHNEIDEN und “injiziert”, obwohl es ein integraler Bestandteil der ist SCHNEIDEN Verantwortung? Was vermisse ich?

    – Nishith

    1. August 2013 um 17:47 Uhr

  • Ich glaube nicht, dass dir etwas fehlt. Spionagebasierter Teilmock ist die beste Option, obwohl Sie könnte Testen Sie einfach beide Methoden zusammen. In meinem Beispiel ging es mehr um das Problem, wenn Methoden in der SCHNEIDEN Erstellen Sie Objekte, für die Sie möglicherweise das Verhalten überprüfen oder Werte bestätigen möchten, und dies könnte durch Refactoring von erreicht werden SCHNEIDEN eine Fabrik oder einen externen Dienst zu verwenden, um diese Objekte zu erstellen, die kann verspottet und injiziert werden.

    – andyb

    2. August 2013 um 20:28 Uhr

Benutzeravatar von fresko
fresko

KURZE ANTWORT

So gehen Sie in Ihrem Fall vor:

int argument = 5; // example with int but could be another type
Mockito.when(mockMyAgent.otherMethod(Mockito.anyInt()).thenReturn(requiredReturnArg(argument));

LANGE ANTWORT

Eigentlich ist das, was Sie tun möchten, zumindest in Java 8 möglich. Vielleicht haben Sie diese Antwort nicht von anderen Leuten erhalten, weil ich Java 8 verwende, das dies zulässt, und diese Frage vor der Veröffentlichung von Java 8 steht (das die Übergabe von Funktionen ermöglicht). , nicht nur Werte für andere Funktionen).

Lassen Sie uns einen Aufruf einer Datenbankabfrage simulieren. Diese Abfrage gibt alle Zeilen von HotelTable zurück, die FreeRoms = X und StarNumber = Y haben. Was ich während des Tests erwarte, ist, dass diese Abfrage eine Liste verschiedener Hotels zurückgibt: Jedes zurückgegebene Hotel hat denselben Wert X und Y, während die andere Werte und ich werde sie nach meinen Bedürfnissen entscheiden. Das folgende Beispiel ist einfach, aber Sie können es natürlich komplexer gestalten.

Also erstelle ich eine Funktion, die unterschiedliche Ergebnisse zurückgibt, aber alle haben FreeRoms = X und StarNumber = Y.

static List<Hotel> simulateQueryOnHotels(int availableRoomNumber, int starNumber) {
    ArrayList<Hotel> HotelArrayList = new ArrayList<>();
    HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Rome, 1, 1));
    HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Krakow, 7, 15));
    HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Madrid, 1, 1));
    HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Athens, 4, 1));

    return HotelArrayList;
}

Vielleicht ist Spy besser (versuchen Sie es bitte), aber ich habe das in einer verspotteten Klasse gemacht. Hier, wie ich es mache (beachten Sie die anyInt () -Werte):

//somewhere at the beginning of your file with tests...
@Mock
private DatabaseManager mockedDatabaseManager;

//in the same file, somewhere in a test...
int availableRoomNumber = 3;
int starNumber = 4;
// in this way, the mocked queryOnHotels will return a different result according to the passed parameters
when(mockedDatabaseManager.queryOnHotels(anyInt(), anyInt())).thenReturn(simulateQueryOnHotels(availableRoomNumber, starNumber));

1431030cookie-checkVerwenden von Mockito zum Stubben und Ausführen von Methoden zum Testen

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy