5

Voglio dividere un test di grandi dimensioni in test più piccoli in modo che quando superano i test più piccoli implicano anche il superamento del test di grandi dimensioni (quindi non c'è motivo di eseguire il test prova originale grande). Voglio farlo perché i test più piccoli di solito richiedono meno tempo, meno sforzo e sono meno fragili. Vorrei sapere se ci sono schemi di progettazione di test o strumenti di verifica che possono aiutarmi a ottenere questo risultato in modo robusto.Divisione di un test a una serie di test più piccoli

Temo che la connessione tra i test più piccoli e il test originale venga persa quando qualcuno cambia qualcosa nel set di test più piccoli. Un altro timore è che il set di test più piccoli non copre realmente il big test.

Un esempio di quello che sto puntando a:

//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); 
} 

risposta

2

Il primo suggerimento che farò è di ridimensionare i test su rosso (in mancanza). Per fare ciò, dovrai interrompere temporaneamente il tuo codice di produzione. In questo modo, sai che i test sono ancora validi.

Un modello comune consiste nell'utilizzare un dispositivo di prova separato per raccolta di test "grandi". Non è necessario attenersi al modello "tutti i test per una classe in una classe di test". Se una serie di test sono correlati tra loro, ma non sono correlati ad un altro insieme di test, quindi inserirli nella loro classe.

Il più grande vantaggio nell'usare una classe separata per contenere i singoli test piccoli per il test di grandi dimensioni è che è possibile sfruttare i metodi di installazione e di rimozione. Nel tuo caso, vorrei spostare le linee che hanno commentato con:

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

al metodo di installazione (in JUnit, un metodo annotato con @Before). Se è necessario eseguire alcune impostazioni insolitamente costose, la maggior parte dei framework di testing xUnit ha un modo per definire un metodo di installazione che viene eseguito una volta prima di tutti i test. In JUnit, questo è un metodo public static void con l'annotazione @BeforeClass.

Se i dati di test sono immutabili, tendo a definire le variabili come costanti.

Mettendo insieme tutto questo, si potrebbe avere qualcosa di simile:

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 Mantenere i test in una stessa classe (e la denominazione della classe di test come avete fatto) renderà meno probabile che qualcuno avrebbe accidentalmente freno il collegamento tra la prova originale e test più piccoli. Grazie per questo. Mi manca ancora un modo automatico per sapere che i test più piccoli implicano il superamento del test. – mkorpela

+0

@mkorpela, puoi approfondire un po 'il motivo per cui è importante che i test più piccoli implichino il superamento del test? Se uno dei test più piccoli fallisce, non è sufficiente per indicare che esiste un problema? In ogni caso, la maggior parte dei corridori sperimentali presentano un'indicazione dello stato per l'intera classe di test. Ad esempio, i runner di test NUnit ed Eclipse JUnit contrassegnano la classe di test come "verdi" se tutti i test nella classe passano e "rossi" se un altro test fallisce. "TheBigTest" verrebbe contrassegnato come passante se tutti i test più piccoli passassero. –

+0

@Hurme, non riesco a rimuovere il test originale se può fallire in una situazione in cui i test più piccoli potrebbero passare. – mkorpela

0

Tutto quello che posso suggerire è il libro xUnit Test Patterns. Se c'è una soluzione dovrebbe essere lì.

0

theBigTest manca la dipendenza B. Anche la dipendenza smallerTest1 gestisce la dipendenza da B. In smallerTest2 dovresti prendere in giro InputFromA.

Perché hai creato un grafico delle dipendenze come hai fatto?

A prende un B poi quando A::processInput, poi post-elaborazione InputFromA in B.

Conservare il test e il refactor grande A e B per modificare il mapping delle dipendenze.

[EDIT] in risposta alle osservazioni.

@mkorpela, il mio punto è che, guardando il codice e le loro dipendenze è come si inizia ad avere un'idea di come creare test più piccoli. A ha una dipendenza su B. In modo che esso per completare il suo process() si deve usare B s' process(). Per questo motivo, B ha una dipendenza su A.

+0

Prima di tutto è solo un esempio (troppo) semplice di ciò a cui sto mirando. Sto cercando di identificare regole/strumenti generali per suddividere i test in modo che il significato del test originale sia ancora lì. theBigTest manca la dipendenza B come il test è il livello più astratto (non ha bisogno di sapere che c'è B ma il costo di non sapere è che il test ci vorrà più tempo per l'esecuzione). – mkorpela

+0

Capisco su semplici esempi, ma il mio punto è che 'B' è un riferimento null quando si esegue' systemUnderTest.process (i) '. Non funzionerà? Potrebbe richiedere due metodi o classi di estrazione su tale comportamento. Potrebbe richiedere un metodo o una classe di estrazione per testare tale comportamento. Inoltre, se esiste la possibilità che i due test siano superati e che ci sia la possibilità che il big test abbia esito negativo, è necessario disporre di un terzo test che verifichi il comportamento anomalo. – Gutzofter

+0

il metodo createSystemUnderTest() inizializzerà systemUnderTest correttamente nell'esempio creato (si supponga che B non sia null). Non è lo stesso metodo utilizzato negli altri test. "se c'è la possibilità che i due test siano superati e che ci sia la possibilità che il big test fallisca, è necessario un terzo test che verifichi il comportamento anomalo". - La cosa principale che sto chiedendo è come assicurarsi che i test più piccoli copriranno il test originale. – mkorpela

Problemi correlati