Wie kann ich Testfälle entwerfen, um Code basierend auf zufälligen Ereignissen abzudecken?

15

Wenn der Code beispielsweise ein zufälliges int von 0 bis 10 generiert und für jedes Ergebnis eine andere Verzweigung verwendet, wie kann man dann eine Testsuite entwerfen, um eine 100% ige Abdeckung der Anweisungen in einem solchen Code zu gewährleisten?

In Java könnte der Code ungefähr so ​​aussehen:

int i = new Random().nextInt(10);
switch(i)
{
    //11 case statements
}
Midhat
quelle

Antworten:

22

Erweitern Sie Davids Antwort, der ich voll und ganz zustimme, dass Sie einen Wrapper für Random erstellen sollten. Ich habe ziemlich genau die gleiche Antwort darüber in einer ähnlichen Frage geschrieben, also hier eine "Cliff's Notes-Version" davon.

Was Sie tun sollten, ist zuerst den Wrapper als Schnittstelle (oder abstrakte Klasse) zu erstellen:

public interface IRandomWrapper {
    int getInt();
}

Und die konkrete Klasse dafür würde so aussehen:

public RandomWrapper implements IRandomWrapper {

    private Random random;

    public RandomWrapper() {
        random = new Random();
    }

    public int getInt() {
        return random.nextInt(10);
    }

}

Angenommen, Ihre Klasse ist die folgende:

class MyClass {

    public void doSomething() {
        int i=new Random().nextInt(10)
        switch(i)
        {
            //11 case statements
        }
    }

}

Um den IRandomWrapper korrekt zu verwenden, müssen Sie Ihre Klasse so ändern, dass sie als Mitglied akzeptiert wird (über einen Konstruktor oder einen Setter):

public class MyClass {

    private IRandomWrapper random = new RandomWrapper(); // default implementation

    public setRandomWrapper(IRandomWrapper random) {
        this.random = random;
    }

    public void doSomething() {
        int i = random.getInt();
        switch(i)
        {
            //11 case statements
        }
    }

}

Sie können jetzt das Verhalten Ihrer Klasse mit dem Wrapper testen, indem Sie den Wrapper verspotten. Sie können dies mit einem spöttischen Framework tun, aber dies ist auch einfach selbst zu tun:

public class MockedRandomWrapper implements IRandomWrapper {

   private int theInt;    

   public MockedRandomWrapper(int theInt) {
       this.theInt = theInt;
   }

   public int getInt() { 
       return theInt;
   }

}

Da Ihre Klasse etwas erwartet, das aussieht wie eine IRandomWrapper, können Sie jetzt die verspottete verwenden, um das Verhalten in Ihrem Test zu erzwingen. Hier einige Beispiele für JUnit-Tests:

@Test
public void testFirstSwitchStatement() {
    MyClass mc = new MyClass();
    IRandomWrapper random = new MockedRandomWrapper(0);
    mc.setRandomWrapper(random);

    mc.doSomething();

    // verify the behaviour for when random spits out zero
}

@Test
public void testFirstSwitchStatement() {
    MyClass mc = new MyClass();
    IRandomWrapper random = new MockedRandomWrapper(1);
    mc.setRandomWrapper(random);

    mc.doSomething();

    // verify the behaviour for when random spits out one
}

Hoffe das hilft.

Spoike
quelle
3
Stimme dem voll und ganz zu. Sie testen ein zufälliges Ereignis, indem Sie die zufällige Natur des Ereignisses entfernen. Dieselbe Theorie kann für Zeitstempel verwendet werden
Richard
3
Hinweis: Diese Methode, einem Objekt das andere Objekt zu geben, das es benötigt, anstatt es instanziieren zu lassen, wird als Abhängigkeitsinjektion
Clement Herreman
23

Sie können (sollten) den Code für die zufällige Generierung in eine Klasse oder Methode einschließen und ihn dann während der Tests verspotten / überschreiben, um den gewünschten Wert festzulegen, damit Ihre Tests vorhersehbar sind.

David
quelle
5

Sie haben einen bestimmten Bereich (0-10) und eine bestimmte Granularität (ganze Zahlen). Beim Testen testen Sie also nicht mit den Zufallszahlen. Sie testen innerhalb einer Schleife, die nacheinander jeden Fall trifft. Ich würde empfehlen, die Zufallszahl in eine Unterfunktion zu übertragen, die die case-Anweisung enthält, mit der Sie die Unterfunktion einfach testen können.

entwurde
quelle
viel besser (weil einfacher) als ich vorgeschlagen hatte, wünschte, ich könnte meine Upvotes übertragen :)
David
Eigentlich solltest du beides machen. Testen Sie mit einem nachgebildeten RandomObject, um jeden Zweig einzeln zu testen, und testen Sie wiederholt mit echtem RandomObject. Ersteres ist ein Unit-Test, letzteres eher ein Integrationstest.
sleske
3

Sie können die PowerMock-Bibliothek verwenden, um die Random-Klasse zu verspotten und ihre nextInt () -Methode zu stoppen, um den erwarteten Wert zurückzugeben. Sie müssen den ursprünglichen Code nicht ändern, wenn Sie dies nicht möchten.

Ich benutze PowerMockito und habe gerade eine ähnliche Methode wie Sie getestet. Der Code, den Sie für den JUnit-Test bereitgestellt haben, sollte ungefähr so ​​aussehen:

@RunWith(PowerMockRunner.class)
@PrepareForTest( { Random.class, ClassUsingRandom.class } ) // Don't forget to prepare the Random class! :)

public void ClassUsingRandomTest() {

    ClassUsingRandom cur;
    Random mockedRandom;

    @Before
    public void setUp() throws Exception {

        mockedRandom = PowerMockito.mock(Random.class);

        // Replaces the construction of the Random instance in your code with the mock.
        PowerMockito.whenNew(Random.class).withNoArguments().thenReturn(mockedRandom);

        cur = new ClassUsingRandom();
    }

    @Test
    public void testSwitchAtZero() {

        PowerMockito.doReturn(0).when(mockedRandom).nextInt(10);

        cur.doSomething();

        // Verify behaviour at case 0
     }

    @Test
    public void testSwitchAtOne() {

        PowerMockito.doReturn(1).when(mockedRandom).nextInt(10);

        cur.doSomething();

        // Verify behaviour at case 1
     }

    (...)

Sie können auch den nextInt (int) -Aufruf unterbrechen, um einen beliebigen Parameter zu erhalten, falls Sie an Ihrem Switch weitere Fälle hinzufügen möchten:

PowerMockito.doReturn(0).when(mockedRandom).nextInt(Mockito.anyInt());

Schön, nicht wahr? :)

LizardCZ
quelle
2

Verwenden Sie QuickCheck ! Ich habe gerade erst damit angefangen und es ist erstaunlich. Wie die meisten coolen Ideen stammt sie von Haskell, aber die Grundidee ist, dass Sie Ihre Testfälle nicht vorkonfektionieren, sondern von Ihrem Zufallsgenerator für Sie erstellen lassen. Auf diese Weise können Sie anstelle der 4-6 Fälle, die Sie wahrscheinlich in xUnit gefunden haben, Hunderte oder Tausende von Eingaben vom Computer versuchen lassen und feststellen, welche nicht den von Ihnen festgelegten Regeln entsprechen.

Auch QuickCheck versucht, einen fehlgeschlagenen Fall zu vereinfachen, damit der einfachste Fall gefunden werden kann, bei dem ein Fehler auftritt. (Und wenn Sie einen fehlerhaften Fall finden, können Sie ihn natürlich auch in einen xUnit-Test integrieren.)

Es scheint mindestens zwei Versionen für Java zu geben, sodass ein Teil kein Problem darstellen sollte.

Zachary K
quelle