Ist in diesem Szenario eine Verspottung für Unit-Tests angebracht?

8

Ich habe ungefähr 20 Methoden in Java geschrieben und alle rufen einige Webdienste auf. Keiner dieser Webdienste ist noch verfügbar. Um mit der serverseitigen Codierung fortzufahren, habe ich die Ergebnisse, die der Webdienst voraussichtlich liefern wird, fest codiert.

Können wir diese Methoden testen? Soweit ich weiß, verspotten Unit-Tests die Eingabewerte und sehen, wie das Programm reagiert. Ist es sinnvoll, sowohl Eingabe- als auch Ausgabewerte zu verspotten?

Bearbeiten:

Die Antworten hier legen nahe, dass ich Unit-Testfälle schreiben sollte.

Wie kann ich es jetzt schreiben, ohne den vorhandenen Code zu ändern?

Betrachten Sie den folgenden Beispielcode (hypothetischer Code):

    public int getAge()
    {
            Service s = locate("ageservice"); // line 1
            int age = s.execute(empId); // line 2
             return age; // line 3

 }

Wie verspotten wir die Ausgabe?

Im Moment kommentiere ich 'Zeile 1' aus und ersetze Zeile 2 durch int age= 50. Ist das richtig ? Kann mich jemand auf den richtigen Weg hinweisen?

Vinoth Kumar CM
quelle

Antworten:

12

Ja.

Verwenden Sie Mocks und Stubs, um das erwartete Verhalten der Webdienste zu simulieren. Überprüfen Sie, ob Ihr Code unter allen erwarteten Grenzwerten und Äquivalenzklassen ordnungsgemäß funktioniert.

BEARBEITEN:

Nein, Sie sollten den Komponententest mit nicht manuell bearbeiten int age= 50. Sie sollten die Abhängigkeitsinjektion verwenden, damit Sie das ServiceObjekt problemlos durch ein Scheinobjekt ersetzen können .

public int getAge(Service s)
{
    int age = s.execute(empId); // line 2
    return age; // line 3
}

Ihr Unit-Test sollte ungefähr so ​​aussehen wie der folgende Pseudocode:

public int getAgeMinimumValueTest()
{
    ClassUnderTest uut = new ClassUnderTest();
    MockService service = new MockService();
    service.age = 10;
    int age = uut.getAge(service);
    Assert(age == 10);
}
M. Dudley
quelle
1
Wenn wir sowohl Input als auch Output verspotten, worum geht es dann? Wie verspotten wir die Ausgabe überhaupt?
Vinoth Kumar CM
1
Wenn Sie eine einzelne Methode Unit-Tests durchführen, sollten Sie für jeden Anwendungsfall einen Unit-Test erstellen. Das heißt, in jedem Komponententest geben Sie unterschiedliche Eingabewerte für die Methode an und überprüfen, ob die Ausgabe der richtige Wert ist. Wenn die Methode von einem Webdienst abhängig ist, verspotten Sie den Webdienst so, dass er sich in diesem Anwendungsfall wie erwartet verhält. Beispielsweise können Sie Scheincode für den Webdienst schreiben, um Werte bereitzustellen, die als Eingaben für die Methode gelten.
M. Dudley
3

Ja, Sie sollten sich über Ihren Service lustig machen. Beim Verspotten werden Mock-Objekte verwendet , um die realen Objekte zu ersetzen, die Sie normalerweise zur Laufzeit verwenden würden. In den meisten Fällen möchten Sie ein Mocking-Framework verwenden, um Mock-Objekte einfach und dynamisch zu erstellen.

Wenn Sie Scheinobjekte verwenden, würden Sie wahrscheinlich Code schreiben, der eher so aussieht:

public int getAge(ServiceInterface service)
{
    return service.execute(empId);
}

Wo serviceist der eigentliche Dienst, den Sie zur Laufzeit verwenden werden, aber während eines Komponententests handelt es sich um ein Scheinobjekt mit Scheinverhalten. Beachten Sie, dass der Parameter für getAgekeine konkrete Klasse mehr ist, sondern eine Schnittstelle. Auf diese Weise können Sie jedes Objekt als Parameter verwenden, solange es die Schnittstelle implementiert, anstatt nur eine bestimmte Klasse. Ihr Komponententest würde dann die folgenden Schritte ausführen:

  1. Erstellen Sie das Scheinobjekt
  2. Sagen Sie ihm, er soll ein bestimmtes Alter zurückgeben, wenn er executeangerufen wird
  3. Rufen Sie getAgemit dem Scheinobjekt als Parameter auf
  4. Verify getAgegibt das richtige Alter zurück.

Eine gute Liste der Java-Mocking-Frameworks finden Sie in dieser Frage zum Stapelüberlauf .

BEARBEITEN

Kommentieren Sie Ihren Code nicht aus und ersetzen Sie ihn nur für Ihre Komponententests durch eine Konstante. Das Problem dabei ist, dass Sie sicherstellen müssen, dass beim Ausführen von Komponententests das Richtige und beim Freigeben an den Kunden das Richtige kommentiert wird. Dies würde zu einem Albtraum für langfristige Wartungszwecke werden, insbesondere wenn Sie beabsichtigen, mehr als zwei Komponententests zu schreiben, für die Verspottungen erforderlich sind. Abgesehen davon sollten Ihre Tests gegen das fertige Produkt durchgeführt werden . Wenn nicht, bedeutet dies, dass Sie nicht den tatsächlich freigegebenen Code testen, was den Zweck von Tests in erster Linie zunichte macht.

Phil
quelle
1

Ja, so ist es. Ihre Aufgabe ist es zu demonstrieren, dass Ihr Code in seiner Umgebung das Richtige tut. Die externen Webdienste sind Teil der Umgebung. Es ist nicht Ihre Aufgabe, ihre ordnungsgemäße Funktion zu testen, sondern die Aufgabe der Personen, die die Dienste schreiben. Die Eingabe / Ausgabe-Zuordnung, die die Tests überprüfen sollten, ist die Eingabe / Ausgabe für Ihren Code. Wenn der Code Eingaben für einen anderen Mitarbeiter erzeugt, um seine Arbeit zu erledigen, ist dies rein zufällig.

Selbst wenn die Webdienste verfügbar sind, ist es wahrscheinlich eine gute Idee, Ihre Komponententests in einer Scheinumgebung anstatt in der realen durchzuführen. Dies macht die Tests einfacher, weniger fragil in Bezug auf den erwarteten Systemkontext und wahrscheinlich auch schneller.

Kilian Foth
quelle
"Wahrscheinlich eine gute Idee"? Ich würde sagen, wesentlich. Sie können keine zuverlässigen Unit-Tests über Systemgrenzen hinweg garantieren.
Thehowler
1

Um die hervorragende Antwort von emddudley zu ergänzen, besteht der größte Vorteil, den Sie durch das Verspotten des Dienstes erzielen können, darin, zu testen, was passieren sollte, wenn der Dienst nicht richtig funktioniert. Der Testpseudocode könnte ungefähr so ​​aussehen:

public int AgeMinimumValue_LogsServiceError_Test()
{
    ClassUnderTest uut = new ClassUnderTest();
    MockService service = new MockService();
    service.Throws(new TimeoutException());

    MockLogger logger = new MockLogger();

    try {
        int age = uut.getAge(service, logger);
        Assert.Fail("Exception was not raised by the class under test");
    }
    catch (TimeoutException) {
        Assert(logger.LogError().WasCalled());
    }
}

Und jetzt wurde Ihre Implementierung mit dieser neuen Anforderung bearbeitet

public int getAge(Service s, Logger l)
{
    try {
        int age = s.execute(empId);
        return age;
    }
    catch(Exception ex) {
        l.LogError(ex);
        throw;
    }
}

In anderen Szenarien ist es wahrscheinlicher, dass Sie auf kompliziertere Antworten reagieren müssen. Wenn der Dienst die Kreditkartenverarbeitung bereitstellt, müssen Sie auf Erfolg, Nicht verfügbarer Dienst, Abgelaufene Kreditkarte, ungültige Nummer usw. reagieren. Durch Verspotten des Dienstes können Sie sicherstellen, dass Sie auf diese Szenarien entsprechend Ihrer Situation reagieren. In diesem Fall müssen Sie die Eingabe / Ausgabe des Dienstes verspotten, und das Feedback, das Sie erhalten, wenn Sie wissen, dass der konsumierende Code für alle bekannten Ausgaben funktioniert, ist in der Tat sinnvoll und wertvoll.

EDIT: Mir ist gerade aufgefallen, dass Sie verspotten möchten, ohne die vorhandene Methode zu ändern. Zu diesem locate("ageservice");Zweck müsste die Methode geändert werden, um Scheinobjekte in Tests zu unterstützen und den tatsächlichen Dienst zu lokalisieren, sobald er bereit ist. Dies ist eine Variation des Service Locator-Musters, das die Logik zum Abrufen der Implementierung des von Ihnen verwendeten Dienstes abstrahiert. Eine schnelle Version davon könnte folgendermaßen aussehen:

public Service locate(string serviceToLocate) {
    if(testEnvironment) // Test environment should be set externally
        return MockService(serviceToLocate);
    else
        return Service(serviceToLocate);
}

Meine Empfehlung wäre jedoch, die Dienstabhängigkeiten in die Konstruktoren zu verschieben:

public int AgeMinimumValue_LogsServiceError_Test()
{
    MockService service = new MockService();
    service.Throws(new TimeoutException());

    MockLogger logger = new MockLogger();

    ClassUnderTest uut = new ClassUnderTest(service, logger);

    try {
        int age = uut.getAge();
        Assert.Fail("Exception was not raised by the class under test");
    }
    catch (TimeoutException) {
        Assert(logger.LogError().WasCalled());
    }
}

Jetzt ist die getAge-Methode nicht mehr dafür verantwortlich, den Dienst nachzuschlagen, da er vollständig aus der Klasse abstrahiert wurde und eine Implementierung wie diese übrig bleibt:

public int getAge()
{
    try {
        // _service is a private field set by the constructor
        int age = _service.execute(empId); 
        return age;
    }
    catch(Exception ex) {
         // _logger is a private field set by the constructor
        _logger.LogError(ex);
        throw;
    }
}
Phil Patterson
quelle
0

Ist es sinnvoll, sowohl Eingabe- als auch Ausgabewerte zu verspotten?

Nein, aber Sie verspotten den Eingabewert nicht. Ihr Test wird ausgeführt, und der Mock-Service überprüft, ob auf den richtigen Teil des Vertrags zugegriffen wird. Dass Sie zufällig einen bestimmten Wert zurückgeben, spielt keine Rolle.

Auf der anderen Seite ist der Rückgabewert wichtig, wenn Ihre Methode überhaupt eine Logik ausführt. Hier verspotten Sie die Eingaben in die Logik (das Ergebnis des Webdienstes) und testen die Ausgabe.

Telastyn
quelle
0

Wie kann ich es jetzt schreiben, ohne den vorhandenen Code zu ändern?

du kannst nicht. Sie können jedoch eine Schnittstelle "IMyService" erstellen, gegen die Sie programmieren können und die alle Webservice-Methodensignaturen enthält.

public int getAge()
{
        IMyService s = getService(); 
        int age = s.execute(empId);
         return age; // line 3

}

Im Produktionsmodus getService();wird ein Verweis auf einen voll funktionsfähigen Webservice und im Testmodus eine alternative Implementierung (oder ein Mock) zurückgegeben, die Ihre gefälschten Daten zurückgibt.

k3b
quelle
0

Beim Verspotten geht es nicht um Ein- oder Ausgänge, sondern darum, externe Abhängigkeiten zu ersetzen. Also ja, es ist angebracht, Unit-Tests zu schreiben und die externen Webdienste zu verspotten.

Nun zu den schlechten Nachrichten: Abhängig von der Sprache und den Tools, die Ihnen zur Verfügung stehen, können Sie die externen Abhängigkeiten möglicherweise nicht verspotten, es sei denn, der Code wurde so konzipiert, dass Sie dies zulassen. Wenn dies der Fall ist, werden Ihre Tests tatsächlich eher zu kleinen Integrationstests als zu reinen Unti-Tests.

Auf der positiven Seite sieht es so aus, als ob Sie den Code ändern dürfen (andernfalls wie kommentieren Sie ihn aus), indem Sie eine Schnittstelle an Ihren Dienst übergeben, damit er verspottet werden kann (oder eine andere Form der Abhängigkeitsinjektion verwenden kann ).

Schließlich scheint das, was Sie testen möchten, ein reiner Wrapper um den externen Dienst zu sein. Hier gibt es fast nichts zu testen, außer dass Sie den Dienst aufrufen. Da dies im Grunde eine Schnittstelle ist, müssen die meisten Tests später auf einer höheren Ebene durchgeführt werden (Integrationstests).

jk.
quelle