Frage

Ich möchte eine abstrakte Klasse testen. Sicher, ich kann manuell ein Mock schreiben, die aus der Klasse erbt.

Kann ich das ein Mockframework mit (ich verwende Mockito) statt von Hand Crafting meine Mock? Wie?

War es hilfreich?

Lösung

Der folgende Vorschlag lässt Sie abstrakte Klassen testen, ohne eine „echte“ Unterklasse zu schaffen -. Die Mock ist die Unterklasse

Verwendung Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS), dann alle abstrakte Methoden verspotten, die aufgerufen werden.

Beispiel:

public abstract class My {
  public Result methodUnderTest() { ... }
  protected abstract void methodIDontCareAbout();
}

public class MyTest {
    @Test
    public void shouldFailOnNullIdentifiers() {
        My my = Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS);
        Assert.assertSomething(my.methodUnderTest());
    }
}

Hinweis: Die Schönheit dieser Lösung ist, dass Sie dies nicht tun Haben die abstrakten Methoden zu implementieren, solange sie nie aufgerufen werden

.

In meiner ehrlichen Meinung, das ist ordentlicher als einen Spion verwenden, da ein Spion eine Instanz benötigt, was bedeutet, Sie haben eine instanzierbare Unterklasse Ihrer abstrakten Klasse erstellen.

Andere Tipps

Wenn Sie nur einige der konkreten Methoden überprüft werden müssen, ohne dass die Abstracts zu berühren, Sie CALLS_REAL_METHODS verwenden können (siehe Morten Antwort ), aber wenn die konkrete Methode im Test einige der Abstracts oder nicht implementierte Schnittstelle Methoden aufruft, wird dies nicht funktionieren - Mockito wird sich beschweren, „nicht nennen kann echte Methode auf Java-Schnittstelle. "

(Ja, es ist ein lausiges Design, aber einige Rahmenbedingungen, beispielsweise Tapestry 4, Art es auf Ihnen erzwingen.)

Die Abhilfe ist dieser Ansatz zu umkehren - das gewöhnliche Mock Verhalten verwenden (das heißt, alles verspottet / Stub) und doCallRealMethod() verwenden, um explizit die konkrete Methode im Test zu rufen. Z.

public abstract class MyClass {
    @SomeDependencyInjectionOrSomething
    public abstract MyDependency getDependency();

    public void myMethod() {
        MyDependency dep = getDependency();
        dep.doSomething();
    }
}

public class MyClassTest {
    @Test
    public void myMethodDoesSomethingWithDependency() {
        MyDependency theDependency = mock(MyDependency.class);

        MyClass myInstance = mock(MyClass.class);

        // can't do this with CALLS_REAL_METHODS
        when(myInstance.getDependency()).thenReturn(theDependency);

        doCallRealMethod().when(myInstance).myMethod();
        myInstance.myMethod();

        verify(theDependency, times(1)).doSomething();
    }
}

Aktualisiert:

Für Nicht-Leere Methoden, Sie verwenden müssen, thenCallRealMethod() statt, zum Beispiel:

when(myInstance.myNonVoidMethod(someArgument)).thenCallRealMethod();

Ansonsten Mockito wird sich beschweren, "Unfinished Anstoßen erkannt."

Sie können dies erreichen, indem er einen Spion mit (die neueste Version von Mockito verwenden 1.8+ obwohl).

public abstract class MyAbstract {
  public String concrete() {
    return abstractMethod();
  }
  public abstract String abstractMethod();
}

public class MyAbstractImpl extends MyAbstract {
  public String abstractMethod() {
    return null;
  }
}

// your test code below

MyAbstractImpl abstractImpl = spy(new MyAbstractImpl());
doReturn("Blah").when(abstractImpl).abstractMethod();
assertTrue("Blah".equals(abstractImpl.concrete()));

Mocking Frameworks sind entworfen, um es einfacher zu machen Abhängigkeiten der Klasse verspotten Sie testen. Wenn Sie einen Mockframework verwenden, um eine Klasse zu verspotten, schaffen die meisten Frameworks dynamisch eine Unterklasse, und ersetzen Sie mit dem Code der Methode Implementierung zum Erfassen, wenn eine Methode aufgerufen wird und einen gefälschten Wert zurück.

Wenn eine abstrakte Klasse zu testen, sollten Sie die nicht-abstrakte Methoden des Faches Under Test (SUT) auszuführen, so dass ein Mockframework ist nicht das, was Sie wollen.

Ein Teil der Verwirrung ist, dass die Antwort auf die Frage, die Sie an den im Zusammenhang mit Hand-craft einen Mock, die von Ihrer abstrakten Klasse erweitert. Ich würde ein Mock nicht nennen eine solche Klasse. Ein Mock ist eine Klasse, die als Ersatz für eine Abhängigkeit verwendet wird, wird mit den Erwartungen programmiert und kann abgefragt werden, um zu sehen, ob diese Erwartungen erfüllt werden.

Stattdessen schlage ich vor, die eine nicht-abstrakte Unterklasse der abstrakten Klasse in Ihrem Test. Wenn das zu viel Code führt, als das ein Zeichen dafür sein kann, dass Ihre Klasse schwierig ist, zu verlängern.

Eine alternative Lösung wäre, Ihren Testfall zu machen selbst abstrakt, mit einer abstrakten Methode für den SUT zu schaffen (in anderen Worten würde der Testfall die Template-Methode Entwurfsmuster).

Versuchen Sie, eine individuelle Antwort verwendet wird.

Zum Beispiel:

import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

public class CustomAnswer implements Answer<Object> {

    public Object answer(InvocationOnMock invocation) throws Throwable {

        Answer<Object> answer = null;

        if (isAbstract(invocation.getMethod().getModifiers())) {

            answer = Mockito.RETURNS_DEFAULTS;

        } else {

            answer = Mockito.CALLS_REAL_METHODS;
        }

        return answer.answer(invocation);
    }
}

Es wird die Mock für abstrakte Methoden geben und wird die reale Methode für konkrete Methoden aufrufen.

Was macht mich wirklich über spöttischen abstrakte Klassen schlecht fühlen, ist die Tatsache, dass weder die Standardkonstruktors YourAbstractClass () (fehlende super () in mock) aufgerufen wird, noch scheint es eine Möglichkeit, in Mockito werden auf Standard initialisieren Mock Eigenschaften ( zB Liste Eigenschaften mit leerer Arraylist oder LinkedList).

Meine abstrakte Klasse (im Grunde die Klasse Quellcode wird generiert) liefert keine Abhängigkeit Setter Injektion für Listenelemente, noch einen Konstruktor, wo sie die Listenelemente initialisiert (was ich versuchte, manuell hinzuzufügen).

Nur das Klassenattribut Verwendung Standardinitialisierung: private List dep1 = new Arraylist; private List DEP2 = new Arraylist

Es gibt also keine Möglichkeit, eine abstrakte Klasse zu verspotten ein reales Objekt Implementierung (zum Beispiel innere Klassendefinition in Unit-Test-Klasse, übergeordnete abstrakte Methoden) und Spionage das reale Objekt ohne Verwendung (das richtige Feld Initialisierung der Fall ist).

Schade, dass nur PowerMock würde hier weiter helfen.

Angenommen, Ihre Testklassen im selben Paket sind (unter einem anderen Quelle root) als Klassen im Test erstellen Sie können einfach die Mock:

YourClass yourObject = mock(YourClass.class);

und die Methoden rufen Sie eine andere Methode, gerade wie Sie testen möchten.

Sie müssen die Erwartungen für die einzelnen Verfahren schaffen, mit der Erwartung, auf irgendwelchen konkreten Methoden aufgerufen wird die Super-Methode aufrufen - nicht sicher, wie Sie das mit Mockito tun würde, aber ich glaube, es ist möglich, mit EasyMock

.

All dies tut, ist ein konkretes Beispiel von YouClass zu schaffen und Ihnen den Aufwand für die Bereitstellung von leeren Implementierungen jeder abstrakten Methode zu speichern.

Als beiseite, ich finde es oft nützlich, um die abstrakte Klasse in meinem Test zu implementieren, wo sie als eine beispielhafte Implementierung dienen, die ich über seine öffentliche Schnittstelle zu testen, obwohl dies auf der Funktionalität von der abstrakten Klasse bereitgestellt abhängen.

Sie können die abstrakte Klasse mit einer anonymen Klasse im Test verlängern. Zum Beispiel (mit JUnit 4):

private AbstractClassName classToTest;

@Before
public void preTestSetup()
{
    classToTest = new AbstractClassName() { };
}

// Test the AbstractClassName methods.

Sie können eine anonyme Klasse instanziiert, Ihre Mocks injizieren und dann diese Klasse testen.

@RunWith(MockitoJUnitRunner.class)
public class ClassUnderTest_Test {

    private ClassUnderTest classUnderTest;

    @Mock
    MyDependencyService myDependencyService;

    @Before
    public void setUp() throws Exception {
        this.classUnderTest = getInstance();
    }

    private ClassUnderTest getInstance() {
        return new ClassUnderTest() {

            private ClassUnderTest init(
                    MyDependencyService myDependencyService
            ) {
                this.myDependencyService = myDependencyService;
                return this;
            }

            @Override
            protected void myMethodToTest() {
                return super.myMethodToTest();
            }
        }.init(myDependencyService);
    }
}

Beachten Sie, dass die Sichtbarkeit für die Eigenschaft protected der abstrakten Klasse myDependencyService ClassUnderTest werden muss.

Whitebox.invokeMethod (..) kann in diesem Fall nützlich sein.

Mockito erlaubt abstrakte Klassen mit Hilfe der @Mock Anmerkung spöttisch:

public abstract class My {

    public abstract boolean myAbstractMethod();

    public void myNonAbstractMethod() {
        // ...
    }
}

@RunWith(MockitoJUnitRunner.class)
public class MyTest {

    @Mock(answer = Answers.CALLS_REAL_METHODS)
    private My my;

    @Test
    private void shouldPass() {
        BDDMockito.given(my.myAbstractMethod()).willReturn(true);
        my.myNonAbstractMethod();
        // ...
    }
}

Der Nachteil ist, dass es nicht verwendet werden kann, wenn Sie Konstruktorparameter benötigen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top