Come posso progettare casi di test per coprire il codice in base a eventi casuali?

15

Ad esempio, se il codice genera un int random da 0-10 e prende un ramo diverso su ciascun risultato, come si può progettare una suite di test per garantire una copertura delle istruzioni 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
}
    
posta Midhat 03.11.2011 - 09:34
fonte

5 risposte

22

Espansione La risposta di David che sono completamente d'accordo sul fatto che dovresti creare un wrapper per Random. Ho scritto più o meno la stessa risposta in precedenza in un domanda simile ecco quindi una" versione per appunti di Cliff ".

Quello che dovresti fare è prima creare 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);
    }

}

Dì che la tua classe è la seguente:

class MyClass {

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

}

Per usare correttamente IRandomWrapper devi modificare la tua classe per prenderla come membro (tramite costruttore o 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, prendendo in giro il wrapper. Puoi farlo con un framework di simulazione, ma questo è facile da fare anche tu:

public class MockedRandomWrapper implements IRandomWrapper {

   private int theInt;    

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

   public int getInt() { 
       return theInt;
   }

}

Dato che la tua classe si aspetta qualcosa che assomigli a IRandomWrapper , ora puoi usare il mocked per forzare il comportamento nel 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 questo aiuti.

    
risposta data 03.11.2011 - 13:25
fonte
23

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

    
risposta data 03.11.2011 - 09:44
fonte
5

Hai un intervallo specificato (0-10) e una granularità specificata (numeri interi). Quindi, quando si esegue il test, non si esegue il test con i numeri casuali. Esegui il test all'interno di un loop che colpisce a turno ogni caso. Ti consiglio di passare il numero casuale in una sottofunzione contenente l'istruzione case, che ti permette di testare semplicemente la sotto funzione.

    
risposta data 03.11.2011 - 10:54
fonte
3

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

Uso PowerMockito e ho appena testato un metodo simile al tuo. Per il codice che hai postato, il test di JUnit dovrebbe essere simile a questo:

@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 eseguire il stub della 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());

Carino, non è vero? :)

    
risposta data 27.12.2011 - 02:09
fonte
2

Utilizza QuickCheck ! Ho appena iniziato a giocare con questo recentemente ed è sorprendente. Come la maggior parte delle idee interessanti proviene da Haskell, ma l'idea di base è che invece di dare ai test i casi di test predefiniti, lascia che il tuo generatore di numeri casuali li costruisca per te. In questo modo, invece dei 4-6 casi che verrebbero probabilmente generati 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 verifica quando trova un caso in errore tenta di semplificarlo in modo che possa trovare il caso più semplice possibile che non riesca. (E ovviamente quando trovi un caso non valido puoi poi crearlo anche in un test xUnit)

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

    
risposta data 03.11.2011 - 14:04
fonte

Leggi altre domande sui tag