Ich möchte eine abstrakte Klasse testen. Klar, ich kann manuell einen Mock schreiben , der von der Klasse erbt.
Kann ich dies mit einem Mocking-Framework (ich verwende Mockito) tun, anstatt mein Mock von Hand zu erstellen? Wie?
java
unit-testing
mocking
abstract-class
mockito
ripper234
quelle
quelle
SomeAbstract spy = spy(SomeAbstract.class);
mock(MyAbstractClass.class, withSettings().useConstructor(arg1, arg2).defaultAnswer(CALLS_REAL_METHODS))
Antworten:
Mit dem folgenden Vorschlag können Sie abstrakte Klassen testen, ohne eine "echte" Unterklasse zu erstellen - der Mock ist die Unterklasse.
Verwenden Sie
Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS)
und verspotten Sie dann alle abstrakten Methoden, die aufgerufen werden.Beispiel:
Hinweis: Die Schönheit dieser Lösung ist , dass Sie nicht haben , um die abstrakten Methoden zu implementieren, solange sie nie aufgerufen.
Meiner ehrlichen Meinung nach ist dies ordentlicher als die Verwendung eines Spions, da ein Spion eine Instanz benötigt, was bedeutet, dass Sie eine instanziierbare Unterklasse Ihrer abstrakten Klasse erstellen müssen.
quelle
Wenn Sie nur einige der konkreten Methoden testen müssen, ohne eines der Abstracts zu berühren, können Sie diese verwenden
CALLS_REAL_METHODS
(siehe Mortens Antwort ). Wenn die zu testende konkrete Methode jedoch einige der Abstracts oder nicht implementierte Schnittstellenmethoden aufruft, funktioniert dies nicht - Mockito wird sich beschweren "Kann keine echte Methode auf der Java-Schnittstelle aufrufen."(Ja, es ist ein mieses Design, aber einige Frameworks, z. B. Tapestry 4, zwingen es Ihnen auf.)
Die Problemumgehung besteht darin, diesen Ansatz umzukehren - verwenden Sie das gewöhnliche Scheinverhalten (dh alles ist verspottet / blockiert) und
doCallRealMethod()
rufen Sie die zu testende konkrete Methode explizit auf. Z.BAktualisiert, um hinzuzufügen:
Für nicht leere Methoden müssen Sie
thenCallRealMethod()
stattdessen Folgendes verwenden , z.Andernfalls beschwert sich Mockito über "Unfertiges Stubbing erkannt".
quelle
Sie können dies mit einem Spion erreichen (verwenden Sie jedoch die neueste Version von Mockito 1.8+).
quelle
Mocking Frameworks sollen es einfacher machen, Abhängigkeiten der Klasse, die Sie testen, auszuspotten. Wenn Sie ein Verspottungsframework zum Verspotten einer Klasse verwenden, erstellen die meisten Frameworks dynamisch eine Unterklasse und ersetzen die Methodenimplementierung durch Code, um zu erkennen, wann eine Methode aufgerufen wird, und einen falschen Wert zurückzugeben.
Wenn Sie eine abstrakte Klasse testen, möchten Sie die nicht abstrakten Methoden des zu testenden Subjekts (Subject Under Test, SUT) ausführen, sodass ein Verspottungsframework nicht das ist, was Sie möchten.
Ein Teil der Verwirrung besteht darin, dass die Antwort auf die Frage, mit der Sie verlinkt haben, darin besteht, einen Schein zu erstellen, der sich von Ihrer abstrakten Klasse erstreckt. Ich würde eine solche Klasse nicht als Mock bezeichnen. Ein Mock ist eine Klasse, die als Ersatz für eine Abhängigkeit verwendet wird, mit Erwartungen programmiert ist und abgefragt werden kann, um festzustellen, ob diese Erwartungen erfüllt sind.
Stattdessen schlage ich vor, in Ihrem Test eine nicht abstrakte Unterklasse Ihrer abstrakten Klasse zu definieren. Wenn dies zu viel Code ergibt, kann dies ein Zeichen dafür sein, dass Ihre Klasse schwer zu erweitern ist.
Eine alternative Lösung wäre, Ihren Testfall selbst abstrakt zu machen, mit einer abstrakten Methode zum Erstellen des SUT (mit anderen Worten, der Testfall würde das Entwurfsmuster der Vorlagenmethode verwenden ).
quelle
Versuchen Sie es mit einer benutzerdefinierten Antwort.
Beispielsweise:
Es wird das Modell für abstrakte Methoden zurückgeben und die reale Methode für konkrete Methoden aufrufen.
quelle
Was mich beim Verspotten abstrakter Klassen wirklich schlecht macht, ist die Tatsache, dass weder der Standardkonstruktor YourAbstractClass () aufgerufen wird (fehlt super () im Mock), noch scheint es in Mockito eine Möglichkeit zu geben, Mock-Eigenschaften (z. B. List-Eigenschaften) standardmäßig zu initialisieren mit leerer ArrayList oder LinkedList).
Meine abstrakte Klasse (im Grunde wird der Klassenquellcode generiert) bietet KEINE Abhängigkeitssetzinjektion für Listenelemente und keinen Konstruktor, in dem die Listenelemente initialisiert werden (die ich manuell hinzugefügt habe).
Nur die Klassenattribute verwenden die Standardinitialisierung: private List dep1 = new ArrayList; private List dep2 = neue ArrayList
Es gibt also KEINE Möglichkeit, eine abstrakte Klasse zu verspotten, ohne eine Implementierung eines realen Objekts zu verwenden (z. B. Definition der inneren Klasse in einer Einheitentestklasse, Überschreiben abstrakter Methoden) und das reale Objekt auszuspionieren (was eine ordnungsgemäße Feldinitialisierung bewirkt).
Schade, dass hier nur PowerMock weiter helfen würde.
quelle
Angenommen, Ihre Testklassen befinden sich im selben Paket (unter einem anderen Quellstamm) wie Ihre zu testenden Klassen, können Sie einfach das Modell erstellen:
und rufen Sie die zu testenden Methoden wie jede andere Methode auf.
Sie müssen Erwartungen für jede Methode angeben, die mit den Erwartungen für konkrete Methoden aufgerufen wird, die die Supermethode aufrufen. Sie sind sich nicht sicher, wie Sie dies mit Mockito tun würden, aber ich glaube, dass dies mit EasyMock möglich ist.
Alles, was Sie tun müssen, ist, eine konkrete Instanz von zu erstellen
YouClass
und Ihnen den Aufwand zu ersparen, leere Implementierungen jeder abstrakten Methode bereitzustellen.Abgesehen davon finde ich es oft nützlich, die abstrakte Klasse in meinem Test zu implementieren, wo sie als Beispielimplementierung dient, die ich über ihre öffentliche Schnittstelle teste, obwohl dies von der Funktionalität abhängt, die von der abstrakten Klasse bereitgestellt wird.
quelle
Sie können die abstrakte Klasse in Ihrem Test um eine anonyme Klasse erweitern. Zum Beispiel (mit Junit 4):
quelle
Mockito erlaubt das Verspotten abstrakter Klassen mittels der
@Mock
Annotation:Der Nachteil ist, dass es nicht verwendet werden kann, wenn Sie Konstruktorparameter benötigen.
quelle
Sie können eine anonyme Klasse instanziieren, Ihre Mocks injizieren und diese Klasse dann testen.
Beachten Sie, dass die Sichtbarkeit
protected
für die EigenschaftmyDependencyService
der abstrakten Klasse gelten mussClassUnderTest
.quelle
PowerMocks
Whitebox.invokeMethod(..)
können in diesem Fall nützlich sein.quelle