2017-06-16 2 views
1

Wir haben unser eigenes Framework erstellt, das die Einrichtung einer Analyse-Pipeline vereinfacht. Jedes Mal, wenn eine Analyse endet, wird finish() aufgerufen. finish() lädt Dateien hoch, die während der Analyse generiert wurden. Um sicherzustellen, dass das Framework korrekt verwendet wurde, haben wir überprüft, dass finish() nicht zweimal aufgerufen wird.So verwenden Sie Mockito, um die Anzahl der Aufrufe einer Funktion zu überprüfen, ohne dass Mockito einen zusätzlichen Anruf tätigt.

Nun möchte ich testen, dass finish() für einen bestimmten Schritt in der Pipeline aufgerufen wird. Ich mache das folgende in meinem Test durch den Aufruf:

verify(consumer).finish(); 

Aber anscheinend verify() ruft auch finish() so eine Ausnahme ausgelöst wird und der Test fehlschlägt.

Nun, meine Frage ist:

  • Wie ich zu vermeiden, die fertig sind() zweimal aufgerufen wird?

EDIT

Eine schnelle Einrichtung des Problems:

Analyse

package mockitoTwice; 

public class Analysis extends Finishable { 
    @Override 
    public void finishHelper() { 
     System.out.println("Calling finishHelper()"); 
    } 
} 

konfektionierbar

package mockitoTwice; 

public abstract class Finishable { 
    protected boolean finished = false; 

    public final void finish() { 
     System.out.println("Calling finish()"); 
     if (finished) { 
      throw new IllegalStateException(); 
     } 
     finished = true; 
     finishHelper(); 
    } 

    public abstract void finishHelper(); 
} 

Pipeline

package mockitoTwice; 

public class Pipeline { 
    private Analysis analysis; 

    public Pipeline(Analysis analysis) { 
     this.analysis = analysis; 
    } 

    public void runAnalyses() { 
     analysis.finish(); 
    } 
} 

PipelineTest

package mockitoTwice; 

import static org.mockito.Mockito.mock; 
import static org.mockito.Mockito.times; 
import static org.mockito.Mockito.verify; 

import org.junit.Test; 

public class PipelineTest { 
    @Test 
    public void test() { 
     Analysis analysis = mock(Analysis.class); 
     Pipeline pipeline = new Pipeline(analysis); 
     pipeline.runAnalyses(); 
     verify(analysis).finish(); 
    } 
} 

Antwort

0

Das Problem kann durch den Fang der Ausnahme des Rahmens wie folgt gelöst werden:

@Rule 
public ExpectedException exception; 

@Test 
public void test() { 
    Analysis analysis = mock(Analysis.class); 
    Pipeline pipeline = new Pipeline(analysis); 
    pipeline.runAnalyses(); 
    exception.expect(IllegalStateException.class); 
    verify(analysis).finish(); 
} 

Wenn finish() zu oft aufgerufen wird, wird das Problem von der Verifizierung wie erwartet behandelt.

Wenn finish() zu oft aufgerufen wird, wird die Ausnahme unter pipeline.runAnalyses() aufgerufen.

Sonst ist der Test erfolgreich.

0

ich es wie folgt überprüfen: verify(object, times(1)).doStuff();

+0

Jemand anderes postete genau den gleichen Vorschlag. Diese Lösung funktioniert nicht. Eine Ausnahme wird weiterhin ausgelöst. Probieren Sie es im obigen Code aus. –

2

Testen von Frameworks haben alle ihre Macken, aber wenn Sie solche Probleme haben, dann ist der erste Schritt, um Ihre Klasse und Test-Design zu beurteilen.

Zuerst stelle ich fest, dass AnalysisTest die Analysis-Klasse nicht wirklich testet. Es verspottet Analyse und testet tatsächlich die Pipeline-Klasse.Ein richtiger Test für Analyse würde wie folgt aussehen:

@Test 
public void testFinishTwice() { 

    Analysis analysis = new Analysis(); 

    try { 
     analysis.finish(); 
     analysis.finish(); 
     Assert.fail(); 
    } catch (IllegalStateException ex) { 
     // Optionally assert something about ex 
    } 
} 

Dies wird den impliziten Vertrag überprüfen, die Analyse Illegal wirft, wenn finish() mehr als einmal aufgerufen wird. Es gibt eine Vielzahl von Lösungen für Ihr Problem und die meisten von ihnen hängen davon ab, dies zu überprüfen.

Als nächstes ist die abstrakte Finisable-Klasse mit einer final finish() -Methode nicht ganz so sicher wie es aussieht. Da die finishHelper-Methode geschützten Zugriff hat, ist sie immer noch direkt für jede Klasse im Paket verfügbar. Wenn in Ihrem Beispiel also Pipeline und Analyse im selben Paket sind, könnte Pipeline den Befehl finishHelper direkt aufrufen. Ich denke, das ist das größte Risiko, dass der Code zweimal aufgerufen wird. Wie einfach wäre es, deine IDE automatisch zu beenden, um sie abzuschließen? Selbst wenn der Komponententest so funktioniert, wie Sie es wollten, kann er dies nicht erfassen.

Nun, da ich das angesprochen habe, können wir an die Wurzel des Problems gelangen. Die Finish-Methode wird als final markiert, sodass Mockito sie nicht überschreiben kann. Normalerweise würde Mockito eine Stub-Methode dafür erstellen, aber hier muss der ursprüngliche Code von Finishable verwendet werden. Ein echter Mock würde nicht einmal "Calling finish()" drucken, wenn der Befehl finish aufgerufen wird. Da es bei der ursprünglichen Implementierung hängen geblieben ist, wird die Methode "real finish" sowohl in der Pipeline als auch durch verify (analysis) aufgerufen. Finish();

Was machen wir also? Es gibt keine perfekte Antwort und es hängt wirklich von Details der Situation ab.

Der einfachste Ansatz wäre, das final-Schlüsselwort auf die finish-Methode zu setzen. Dann müssen Sie nur sicherstellen, dass Analyse und Pipeline nicht im selben Paket sind. Der Test, den Sie geschrieben haben, stellt sicher, dass die Pipeline nur einmal beendet wird. Der Test, den ich vorgeschlagen habe, garantiert eine Ausnahme, wenn das zweite Mal die Analyse aufgerufen wird. Dies geschieht sogar, wenn es den Abschluss überschreiben würde. Du könntest es immer noch missbrauchen, aber du müsstest dich absichtlich dafür einsetzen.

Sie können auch Finishable zu einer Schnittstelle wechseln und Ihre aktuelle Klasse AbstractFinishable als Basisimplementierung umbenennen. Wechseln Sie dann zu Analysis zu einer Schnittstelle, die Finishable erweitert, und erstellen Sie eine ExampleAnalysis-Klasse, die AbstractFinishable erweitert und Analysis implementiert. Dann verweist Pipeline auf die Analysis-Schnittstelle. Wir müssen es so machen, denn sonst könnte es Zugang zu finishHelper bekommen und wir sind wieder da, wo wir angefangen haben. Hier ist eine Skizze des Codes:

public interface Finishable { 
    public void finish(); 
} 

public abstract class AbstractFinishable implements Finishable { 
    // your current Finishable class with final finish method goes here                             
} 

public interface Analysis extends Finishable { 
    // Other analysis methods that Pipeline needs go here                           
} 

public ExampleAnalysis extends AbstractFinishable implements Analysis { 
    // Implementations of Analysis methods go here                            
} 

Also das ist eine Möglichkeit, es zu tun. Im Wesentlichen werden die zu codierenden Klassen in Schnittstellen ihrer Abhängigkeiten und nicht in bestimmte Klassenimplementierungen umgeschaltet. Das ist im Allgemeinen einfacher zu testen und zu testen. Sie könnten auch das delegate-Muster verwenden und nur eine Finishable-Anweisung in ExampleAnalysis einfügen, anstatt AbstractFinishable zu erweitern. Es gibt auch andere Wege, und das sind nur Ideen. Sie sollten die Einzelheiten Ihres Projekts gut genug kennen, um die beste Route zu bestimmen.

+0

Dies sind einige scharfe Beobachtungen. Leider, wrt. Wenn Sie die Analyse richtig testen, haben Sie Recht. Aber die vorher gepostete Skizze war falsch. Wrt. Ihr Vorschlag zum Framework, es ist eine brillante Idee, die ich ausprobieren werde. Vielen Dank. –

Verwandte Themen