Come posso progettare casi di test per coprire il codice basato su eventi casuali?


15

Ad esempio, se il codice genera un int casuale da 0 a 10 e accetta un ramo diverso per ogni risultato, come si può progettare una suite di test per garantire una copertura delle dichiarazioni del 100% in tale codice?

In Java, il codice potrebbe essere qualcosa del tipo:

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

Risposte:


22

Espandendo la risposta di David con la quale concordo pienamente che dovresti creare un wrapper per Random. Ho scritto praticamente la stessa risposta in precedenza in una domanda simile, quindi ecco una "versione di Cliff's notes".

Quello che dovresti fare è creare prima il wrapper come interfaccia (o classe astratta):

public interface IRandomWrapper {
    int getInt();
}

E la classe concreta per questo sarebbe simile a questa:

public RandomWrapper implements IRandomWrapper {

    private Random random;

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

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

}

Supponiamo che la tua lezione sia la seguente:

class MyClass {

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

}

Per utilizzare correttamente IRandomWrapper è necessario modificare la classe per prenderla come membro (tramite il costruttore o un 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
        }
    }

}

Ora puoi testare il comportamento della tua classe con il wrapper, deridendo il wrapper. Puoi farlo con un framework beffardo, ma anche questo è facile da fare da solo:

public class MockedRandomWrapper implements IRandomWrapper {

   private int theInt;    

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

   public int getInt() { 
       return theInt;
   }

}

Dal momento che la tua classe si aspetta qualcosa che assomigli a uno IRandomWrapper, ora puoi usare quello deriso per forzare il comportamento nel tuo test. Ecco alcuni esempi di test JUnit:

@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
}

Spero che sia di aiuto.


3
Totalmente d'accordo con questo. Si verifica un evento casuale rimuovendo la natura casuale dell'evento. La stessa teoria può essere usata per i timestamp
Richard,

3
Nota: questa tecnica, di dare a un oggetto l'altro oggetto di cui ha bisogno, invece di lasciarlo istanziare, si chiama Iniezione delle dipendenze
Clemente Herreman,

23

È possibile (dovrebbe) racchiudere il codice di generazione casuale in una classe o in un metodo e quindi deriderlo / sovrascriverlo durante i test per impostare il valore desiderato, in modo che i test siano prevedibili.


5

Hai un intervallo specificato (0-10) e una granularità specificata (numeri interi). Quindi, durante il test, non esegui il test con i numeri casuali. Si esegue il test all'interno di un loop che colpisce a turno ogni caso. Consiglio di passare il numero casuale in una sotto-funzione contenente l'istruzione case, che consente di testare semplicemente la sotto-funzione.


molto meglio (perché più semplice) di quello che ho suggerito, vorrei poter trasferire i miei voti :)
David

In realtà dovresti fare entrambe le cose. Prova con un RandomObject finto per testare ogni ramo individualmente e testalo ripetutamente con RandomObject reale. Il primo è un test unitario, il secondo più simile a un test di integrazione.
sleske,

3

È possibile utilizzare la libreria PowerMock per deridere la classe Random e stub il suo metodo nextInt () per restituire il valore previsto. Non è necessario modificare il codice originale se non si desidera.

Sto usando PowerMockito e ho appena testato un metodo simile al tuo. Per il codice che hai pubblicato il test JUnit dovrebbe essere simile al seguente:

@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
     }

    (...)

Puoi anche stub la chiamata nextInt (int) per ricevere qualsiasi parametro, nel caso in cui desideri aggiungere più casi al tuo switch:

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

Piuttosto no? :)


2

Usa QuickCheck ! Ho appena iniziato a giocarci recentemente ed è fantastico. Come la maggior parte delle idee interessanti viene da Haskell, ma l'idea di base è che invece di fornire i casi di test preconfezionati per il test, lasci che il tuo generatore di numeri casuali li costruisca per te. In questo modo, invece dei 4-6 casi che probabilmente verresti fuori in xUnit, puoi far provare al computer centinaia o migliaia di input e vedere quali non sono conformi alle regole che hai impostato.

Anche QuickCheck, quando trova un caso difettoso, cercherà di semplificarlo in modo che possa trovare il caso più semplice possibile che fallisce. (E ovviamente quando trovi un caso fallito puoi anche inserirlo in un test xUnit)

Sembra che ci siano almeno due versioni per Java, quindi quella parte non dovrebbe essere un problema.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.