Erweiterte JMockit-Nutzung

Erweiterte Nutzung von JMockit

1. Einführung

In diesem Artikel gehen wir über die JMockit-Grundlagen hinaus und beschäftigen uns mit einigen erweiterten Szenarien, wie z.

  • Fälschung (oder dieMockUp API)

  • Die DienstprogrammklasseDeencapsulation

  • So verspotten Sie mehr als eine Benutzeroberfläche mit nur einer Verspottung

  • Wiederverwenden von Erwartungen und Überprüfungen

Wenn Sie die Grundlagen von JMockit kennenlernen möchten, lesen Sie andere Artikel aus dieser Reihe. Sie finden relevante Links unten auf der Seite.

2. Private Methoden / Verspottung innerer Klassen

Das Verspotten und Testen privater Methoden oder innerer Klassen wird oft nicht als gute Praxis angesehen.

Der Grund dafür ist, dass sie, wenn sie privat sind, nicht direkt getestet werden sollten, da sie der innerste Mut der Klasse sind. Manchmal muss dies jedoch noch getan werden, insbesondere wenn es sich um Legacy-Code handelt.

Mit JMockit haben Sie zwei Möglichkeiten, um diese Probleme zu lösen:

  • DieMockUp API zum Ändern der tatsächlichen Implementierung (für den zweiten Fall)

  • Die DienstprogrammklasseDeencapsulationzum direkten Aufrufen einer beliebigen Methode (für den ersten Fall)

Alle folgenden Beispiele werden für die folgende Klasse ausgeführt, und wir gehen davon aus, dass sie in einer Testklasse mit derselben Konfiguration wie die erste ausgeführt werden (um zu vermeiden, dass sich Code wiederholt):

public class AdvancedCollaborator {
    int i;
    private int privateField = 5;

    // default constructor omitted

    public AdvancedCollaborator(String string) throws Exception{
        i = string.length();
    }

    public String methodThatCallsPrivateMethod(int i) {
        return privateMethod() + i;
    }
    public int methodThatReturnsThePrivateField() {
        return privateField;
    }
    private String privateMethod() {
        return "default:";
    }

    class InnerAdvancedCollaborator {...}
}

2.1. Fälschung mitMockUp

Die Mockup-API von JMockit bietet Unterstützung für die Erstellung gefälschter Implementierungen odermock-ups. In der Regel zielt einmock-upauf einige Methoden und / oder Konstruktoren in der zu fälschenden Klasse ab, während die meisten anderen Methoden und Konstruktoren unverändert bleiben. Dies ermöglicht ein vollständiges Umschreiben einer Klasse, sodass auf jede Methode oder jeden Konstruktor (mit jedem Zugriffsmodifikator) abgezielt werden kann.

Mal sehen, wie wirprivateMethod() mithilfe der Mockup-API neu definieren können:

@RunWith(JMockit.class)
public class AdvancedCollaboratorTest {

    @Tested
    private AdvancedCollaborator mock;

    @Test
    public void testToMockUpPrivateMethod() {
        new MockUp() {
            @Mock
            private String privateMethod() {
                return "mocked: ";
            }
        };
        String res = mock.methodThatCallsPrivateMethod(1);
        assertEquals("mocked: 1", res);
    }
}

In diesem Beispiel definieren wir ein neuesMockUp für die KlasseAdvancedCollaborator unter Verwendung der Annotation@Mock für eine Methode mit übereinstimmender Signatur. Danach werden Aufrufe dieser Methode an unsere verspottete Methode delegiert.

Wir können dies auch verwenden, ummock-upden Konstruktor einer Klasse zu bilden, die bestimmte Argumente oder Konfigurationen benötigt, um Tests zu vereinfachen:

@Test
public void testToMockUpDifficultConstructor() throws Exception{
    new MockUp() {
        @Mock
        public void $init(Invocation invocation, String string) {
            ((AdvancedCollaborator)invocation.getInvokedInstance()).i = 1;
        }
    };
    AdvancedCollaborator coll = new AdvancedCollaborator(null);
    assertEquals(1, coll.i);
}

In diesem Beispiel sehen wir, dass Sie zum Verspotten von Konstruktoren die Methode$initverspotten müssen. Sie können ein zusätzliches Argument vom TypInvocation, übergeben, mit dem Sie auf Informationen zum Aufruf der verspotteten Methode zugreifen können, einschließlich der Instanz, für die der Aufruf ausgeführt wird.

2.2. Verwenden derDeencapsulation-Klasse

JMockit enthält eine Testdienstprogrammklasse:Deencapsulation. Wie der Name schon sagt, wird es zum Entkapseln eines Status eines Objekts verwendet. Mit ihm können Sie das Testen vereinfachen, indem Sie auf Felder und Methoden zugreifen, auf die sonst nicht zugegriffen werden könnte.

Sie können eine Methode aufrufen:

@Test
public void testToCallPrivateMethodsDirectly(){
    Object value = Deencapsulation.invoke(mock, "privateMethod");
    assertEquals("default:", value);
}

Sie können auch Felder festlegen:

@Test
public void testToSetPrivateFieldDirectly(){
    Deencapsulation.setField(mock, "privateField", 10);
    assertEquals(10, mock.methodThatReturnsThePrivateField());
}

Und Felder bekommen:

@Test
public void testToGetPrivateFieldDirectly(){
    int value = Deencapsulation.getField(mock, "privateField");
    assertEquals(5, value);
}

Und erstelle neue Instanzen von Klassen:

@Test
public void testToCreateNewInstanceDirectly(){
    AdvancedCollaborator coll = Deencapsulation
      .newInstance(AdvancedCollaborator.class, "foo");
    assertEquals(3, coll.i);
}

Sogar neue Instanzen innerer Klassen:

@Test
public void testToCreateNewInnerClassInstanceDirectly(){
    InnerCollaborator inner = Deencapsulation
      .newInnerInstance(InnerCollaborator.class, mock);
    assertNotNull(inner);
}

Wie Sie sehen können, ist die KlasseDeencapsulationbeim Testen luftdichter Klassen äußerst nützlich. Ein Beispiel könnte sein, Abhängigkeiten einer Klasse festzulegen, die@Autowired Annotationen für private Felder verwendet und keine Setter für diese hat, oder innere Klassen zu testen, ohne von der öffentlichen Schnittstelle ihrer Containerklasse abhängig zu sein.

3. Verspotten mehrerer Schnittstellen in einem Mock

Nehmen wir an, Sie möchten eine noch nicht implementierte Klasse testen, wissen aber sicher, dass sie mehrere Schnittstellen implementiert.

Normalerweise können Sie diese Klasse vor der Implementierung nicht testen, aber mit JMockit können Sie Tests im Voraus vorbereiten, indem Sie mehr als eine Schnittstelle mit einem Scheinobjekt verspotten.

Dies kann erreicht werden, indem Generika verwendet und ein Typ definiert wird, der mehrere Schnittstellen erweitert. Dieser generische Typ kann entweder für eine ganze Testklasse oder nur für eine Testmethode definiert werden.

Zum Beispiel werden wir ein Modell für die SchnittstellenList undComparable auf zwei Arten: erstellen

@RunWith(JMockit.class)
public class AdvancedCollaboratorTest & Comparable>> {

    @Mocked
    private MultiMock multiMock;

    @Test
    public void testOnClass() {
        new Expectations() {{
            multiMock.get(5); result = "foo";
            multiMock.compareTo((List) any); result = 0;
        }};
        assertEquals("foo", multiMock.get(5));
        assertEquals(0, multiMock.compareTo(new ArrayList<>()));
    }

    @Test
    public  & Comparable>>
      void testOnMethod(@Mocked M mock) {
        new Expectations() {{
            mock.get(5); result = "foo";
            mock.compareTo((List) any); result = 0;
        }};
        assertEquals("foo", mock.get(5));
        assertEquals(0, mock.compareTo(new ArrayList<>()));
    }
}

Wie Sie in Zeile 2 sehen können, können wir einen neuen Testtyp für den gesamten Test definieren, indem wir Generika für den Klassennamen verwenden. Auf diese Weise sindMultiMock als Typ verfügbar und Sie können mithilfe einer der Anmerkungen von JMockit Verspottungen dafür erstellen.

In den Zeilen 7 bis 18 sehen wir ein Beispiel mit einem Mock einer Mehrfachklasse, der für die gesamte Testklasse definiert ist.

Wenn Sie das Multi-Interface-Mock für nur einen Test benötigen, können Sie dies erreichen, indem Sie den generischen Typ in der Methodensignatur definieren und ein neues Mock dieses neuen Generikums als Testmethodenargument übergeben. In den Zeilen 20 bis 32 sehen wir ein Beispiel für dasselbe getestete Verhalten wie im vorherigen Test.

4. Wiederverwenden von Erwartungen und Überprüfungen

Am Ende können beim Testen von Klassen Fälle auftreten, in denen Sie dieselbenExpectations und / oderVerifications immer wieder wiederholen. Um dies zu vereinfachen, können Sie beide problemlos wiederverwenden.

Wir werden es anhand eines Beispiels erklären (wir verwenden die KlassenModel, Collaborator undPerformer aus unseremJMockit 101-Artikel):

@RunWith(JMockit.class)
public class ReusingTest {

    @Injectable
    private Collaborator collaborator;

    @Mocked
    private Model model;

    @Tested
    private Performer performer;

    @Before
    public void setup(){
        new Expectations(){{
           model.getInfo(); result = "foo"; minTimes = 0;
           collaborator.collaborate("foo"); result = true; minTimes = 0;
        }};
    }

    @Test
    public void testWithSetup() {
        performer.perform(model);
        verifyTrueCalls(1);
    }

    protected void verifyTrueCalls(int calls){
        new Verifications(){{
           collaborator.receive(true); times = calls;
        }};
    }

    final class TrueCallsVerification extends Verifications{
        public TrueCallsVerification(int calls){
            collaborator.receive(true); times = calls;
        }
    }

    @Test
    public void testWithFinalClass() {
        performer.perform(model);
        new TrueCallsVerification(1);
    }
}

In diesem Beispiel sehen Sie in den Zeilen 15 bis 18, dass wir für jeden Test eine Erwartung vorbereiten, sodassmodel.getInfo() immer“foo” zurückgibt undcollaborator.collaborate() immer“foo”erwartet ) s als Argument und Rückgabe vontrue. Wir setzen die AnweisungminTimes = 0o, dass keine Fehler auftreten, wenn sie nicht tatsächlich in Tests verwendet werden.

Außerdem haben wir die MethodeverifyTrueCalls(int) erstellt, um die Überprüfung der Methodecollaborator.receive(boolean) zu vereinfachen, wenn das übergebene Argumenttrue lautet.

Schließlich können Sie auch neue Arten spezifischer Erwartungen und Überprüfungen erstellen, indem Sie nur eine der KlassenExpectations oderVerificationserweitern. Dann definieren Sie einen Konstruktor, wenn Sie das Verhalten konfigurieren und eine neue Instanz dieses Typs in einem Test erstellen müssen, wie wir es in den Zeilen 33 bis 43 tun.

5. Fazit

Mit diesem Teil der JMockit-Reihe haben wir einige fortgeschrittene Themen angesprochen, die Ihnen beim alltäglichen Verspotten und Testen auf jeden Fall helfen werden.

Wir werden möglicherweise mehr Artikel über JMockit schreiben, also bleiben Sie dran, um noch mehr zu erfahren.

Und wie immer finden Sie die vollständige Implementierung dieses Tutorials inover on GitHub.