5

Ich möchte einen großen Test auf kleinere Tests aufteilen, so dass, wenn die kleineren Tests bestanden haben, sie auch bedeuten, dass der große Test bestanden wird (also gibt es keinen Grund, das zu testen) ursprünglicher großer Test). Ich möchte dies tun, weil kleinere Tests normalerweise weniger Zeit, weniger Aufwand und weniger anfällig sind. Ich würde gerne wissen, ob es Testdesignmuster oder Verifikationswerkzeuge gibt, die mir helfen können, diese Testspaltung auf eine robuste Art und Weise zu erreichen.Einen Test auf eine Reihe kleinerer Tests aufteilen

Ich befürchte, dass die Verbindung zwischen den kleineren Tests und dem ursprünglichen Test verloren geht, wenn jemand etwas in den kleineren Tests ändert. Eine weitere Befürchtung ist, dass der Satz kleinerer Tests den großen Test nicht wirklich abdeckt.

Ein Beispiel dessen, was ich bin mit dem Ziel:

//Class under test 
class A { 

    public void setB(B b){ this.b = b; } 

    public Output process(Input i){ 
    return b.process(doMyProcessing(i)); 
    } 

    private InputFromA doMyProcessing(Input i){ .. } 

    .. 

} 

//Another class under test 
class B { 

    public Output process(InputFromA i){ .. } 

    .. 

} 

//The Big Test 
@Test 
public void theBigTest(){ 
A systemUnderTest = createSystemUnderTest(); // <-- expect that this is expensive 

Input i = createInput(); 

Output o = systemUnderTest.process(i); // <-- .. or expect that this is expensive 

assertEquals(o, expectedOutput()); 
} 

//The splitted tests 

@PartlyDefines("theBigTest") // <-- so something like this should come from the tool.. 
@Test 
public void smallerTest1(){ 
    // this method is a bit too long but its just an example.. 
    Input i = createInput(); 
    InputFromA x = expectedInputFromA(); // this should be the same in both tests and it should be ensured somehow 
    Output expected = expectedOutput(); // this should be the same in both tests and it should be ensured somehow 

    B b = mock(B.class); 
    when(b.process(x)).thenReturn(expected); 

    A classUnderTest = createInstanceOfClassA(); 
    classUnderTest.setB(b); 

    Output o = classUnderTest.process(i); 

    assertEquals(o, expected); 
    verify(b).process(x); 
    verifyNoMoreInteractions(b); 
} 

@PartlyDefines("theBigTest") // <-- so something like this should come from the tool.. 
@Test 
public void smallerTest2(){ 
    InputFromA x = expectedInputFromA(); // this should be the same in both tests and it should be ensured somehow 
    Output expected = expectedOutput(); // this should be the same in both tests and it should be ensured somehow 

    B classUnderTest = createInstanceOfClassB(); 

    Output o = classUnderTest.process(x); 

    assertEquals(o, expected); 
} 

Antwort

2

Der erste Vorschlag, den ich machen werde, besteht darin, Ihre Tests auf Rot (failing) zu re-Faktor. Um dies zu tun, müssen Sie Ihren Produktionscode vorübergehend unterbrechen. Auf diese Weise wissen Sie, dass die Tests noch gültig sind.

Ein übliches Muster ist die Verwendung einer separaten Testvorrichtung für jede Sammlung von "großen" Tests. Sie müssen sich nicht an das Muster "alle Tests für eine Klasse in einer Testklasse" halten. Wenn eine Reihe von Tests miteinander in Beziehung stehen, aber nicht mit einer anderen Gruppe von Tests in Beziehung stehen, dann legen Sie sie in ihre eigene Klasse.

Der größte Vorteil bei der Verwendung einer separaten Klasse für die einzelnen kleinen Tests für den großen Test besteht darin, dass Sie die Vorteile von Setup- und Abnutzungsmethoden nutzen können. In Ihrem Fall würde ich die Linien bewegen Sie kommentiert haben:

// this should be the same in both tests and it should be ensured somehow

auf die Setup-Methode (in JUnit, ein Verfahren mit @Before kommentierten). Wenn Sie ein ungewöhnlich kostspieliges Setup haben, das erledigt werden muss, haben die meisten xUnit-Testframeworks eine Möglichkeit, eine Setup-Methode zu definieren, die vor allen Tests einmal ausgeführt wird. In JUnit ist dies eine public static void-Methode, die die @BeforeClass Annotation hat.

Wenn die Testdaten unveränderlich sind, tendiere ich dazu, die Variablen als Konstanten zu definieren.

alles Putting zusammen, man könnte so etwas wie haben:

public class TheBigTest { 

    // If InputFromA is immutable, it could be declared as a constant 
    private InputFromA x; 
    // If Output is immutable, it could be declared as a constant 
    private Output expected; 

    // You could use 
    // @BeforeClass public static void setupExpectations() 
    // instead if it is very expensive to setup the data 
    @Before 
    public void setUpExpectations() throws Exception { 
     x = expectedInputFromA(); 
     expected = expectedOutput(); 
    } 

    @Test 
    public void smallerTest1(){ 
     // this method is a bit too long but its just an example.. 
     Input i = createInput(); 

     B b = mock(B.class); 
     when(b.process(x)).thenReturn(expected); 

     A classUnderTest = createInstanceOfClassA(); 
     classUnderTest.setB(b); 

     Output o = classUnderTest.process(i); 

     assertEquals(o, expected); 
     verify(b).process(x); 
     verifyNoMoreInteractions(b); 
    } 

    @Test 
    public void smallerTest2(){ 
     B classUnderTest = createInstanceOfClassB(); 

     Output o = classUnderTest.process(x); 

     assertEquals(o, expected); 
    } 

} 
+0

+1 die Tests in der gleichen Klasse zu halten (und die Testklasse zu benennen, wie Sie getan haben) wird es weniger wahrscheinlich machen, dass jemand würde versehentlich Bremse der Verbindung zwischen dem Original-Test und kleineren Tests. Danke dafür. Ich vermisse immer noch automatisch, dass die kleineren Tests bedeuten, dass der große Test bestanden wird. – mkorpela

+0

@mkorpela, können Sie ein wenig darauf eingehen, warum es wichtig ist, dass die kleineren Tests bedeuten, dass der große Test bestehen würde? Wenn einer der kleineren Tests fehlschlägt, reicht das nicht aus, um anzuzeigen, dass ein Problem vorliegt? In jedem Fall haben die meisten Testläufer eine Statusanzeige für die gesamte Testklasse. Zum Beispiel markieren die NUnit- und Eclipse-JUnit-Testläufer die Testklasse als "grün", wenn alle Tests in der Klasse bestanden werden, und "rot", wenn ein weiterer Test fehlschlägt. "TheBigTest" würde als bestanden gekennzeichnet werden, wenn alle kleineren Tests bestanden werden. –

+0

@Hurme, ich kann den Originaltest nicht entfernen, wenn er in einer Situation fehlschlägt, in der die kleineren Tests bestanden werden. – mkorpela

0

Alles, was ich kann, ist das Buch vorschlagen xUnit Test Patterns. Wenn es eine Lösung gibt, sollte es da drin sein.

0

theBigTest ist die Abhängigkeit von B fehlt. Auch smallerTest1 Mocks B Abhängigkeit. In smallerTest2 sollten Sie InputFromA spotten.

Warum haben Sie einen Abhängigkeitsgraphen wie Sie erstellt?

A nimmt ein B dann, wenn A::processInput Sie dann Prozess schreiben InputFromA in B.

Behalten Sie den großen Test und Refactor A und B, um die Abhängigkeitszuordnung zu ändern.

[EDIT] als Reaktion auf Bemerkungen.

@mkorpela, ist mein Punkt, dass auf den Code und deren Abhängigkeiten von der Suche ist, wie Sie eine Vorstellung davon, wie man beginnen kleinere Tests zu erstellen. A hat eine Abhängigkeit von B. Um die process() zu vervollständigen, muss Bprocess() verwendet werden. Aus diesem Grund hat B eine Abhängigkeit von A.

+0

Erstens ist es nur ein sehr (zu) einfaches Beispiel dessen, was ich anstrebe. Ich versuche, allgemeine Regeln/Tools für Splitting-Tests zu identifizieren, so dass die Bedeutung des ursprünglichen Tests immer noch da ist. theBigTest fehlt die Abhängigkeit von B wie der Test auf abstrakteren Ebene ist (es muss wissen nicht, dass es B, aber die Kosten nicht zu wissen, ist, dass der Test länger auszuführen nehmen). – mkorpela

+0

Ich verstehe einfache Beispiele, aber mein Punkt ist, dass "B" eine Nullreferenz ist, wenn Sie 'systemUnderTest.process (i)' ausführen. Es wird nicht laufen? Es kann erfordern, dass Sie zwei Methoden oder Klassen für dieses Verhalten extrahieren. Es kann eine Extraktionsmethode oder -klasse erfordern, um dieses Verhalten zu testen. Auch, wenn die Möglichkeit besteht, dass die beiden Tests bestehen, und es besteht die Möglichkeit, dass der große Test wird fehlschlagen, müssen Sie einen dritten Test haben, die in Ermangelung eines solchen Verhaltenstests. – Gutzofter

+0

Meine createSystemUnderTest() -Methode wird systemUnderTest im erfundenen Beispiel korrekt initialisieren (angenommen, dass B nicht null sein wird). Es ist nicht die gleiche Methode wie in den anderen Tests. "Wenn die Möglichkeit besteht, dass die beiden Tests bestanden werden und der große Test fehlschlägt, müssen Sie einen dritten Test durchführen, der das fehlgeschlagene Verhalten testet." - Hauptsache, ich möchte sicherstellen, dass die kleineren Tests den ursprünglichen Test abdecken. – mkorpela