È corretto ripetere il codice per i test unitari?

11

Ho scritto alcuni algoritmi di ordinamento per un compito di classe e ho anche scritto alcuni test per assicurarmi che gli algoritmi fossero implementati correttamente. I miei test sono lunghi solo 10 righe e ce ne sono 3 ma solo 1 riga cambia tra i 3 quindi c'è un sacco di codice ripetuto. È meglio refactoring questo codice in un altro metodo che viene poi chiamato da ogni test? Non dovrei quindi scrivere un altro test per testare il refactoring? Alcune delle variabili possono anche essere spostate fino al livello di classe. Il test di classi e metodi dovrebbe seguire le stesse regole delle classi / metodi regolari?

Ecco un esempio:

    [TestMethod]
    public void MergeSortAssertArrayIsSorted()
    {
        int[] a = new int[1000];
        Random rand = new Random(DateTime.Now.Millisecond);
        for(int i = 0; i < a.Length; i++)
        {
            a[i] = rand.Next(Int16.MaxValue);
        }
        int[] b = new int[1000];
        a.CopyTo(b, 0);
        List<int> temp = b.ToList();
        temp.Sort();
        b = temp.ToArray();

        MergeSort merge = new MergeSort();
        merge.mergeSort(a, 0, a.Length - 1);
        CollectionAssert.AreEqual(a, b);
    }
    [TestMethod]
    public void InsertionSortAssertArrayIsSorted()
    {
        int[] a = new int[1000];
        Random rand = new Random(DateTime.Now.Millisecond);
        for (int i = 0; i < a.Length; i++)
        {
            a[i] = rand.Next(Int16.MaxValue);
        }
        int[] b = new int[1000];
        a.CopyTo(b, 0);
        List<int> temp = b.ToList();
        temp.Sort();
        b = temp.ToArray();

        InsertionSort merge = new InsertionSort();
        merge.insertionSort(a);
        CollectionAssert.AreEqual(a, b); 
    }
    
posta Pete 18.03.2012 - 20:44
fonte

5 risposte

21

Il codice di test è ancora codice e deve essere mantenuto.

Se hai bisogno di cambiare la logica copiata, devi farlo in ogni posto in cui l'hai copiato, normalmente.

DRY si applica ancora.

Wouldn't I then need to write another test to test the refactoring?

Vuoi? E come sai che i test che hai attualmente sono corretti?

Esegui il test del refactoring eseguendo i test. Dovrebbero avere tutti gli stessi risultati.

    
risposta data 18.03.2012 - 20:49
fonte
11

Come già affermato da Oded, il codice di test deve ancora essere mantenuto. Aggiungo che la ripetizione nel codice di test rende più difficile per i maintainer comprendere la struttura dei test e aggiungere nuovi test.

Nelle due funzioni che hai postato, le seguenti righe sono assolutamente identiche ad eccezione di una differenza di spazio all'inizio del ciclo for :

        int[] a = new int[1000];
        Random rand = new Random(DateTime.Now.Millisecond);
        for (int i = 0; i < a.Length; i++)
        {
            a[i] = rand.Next(Int16.MaxValue);
        }
        int[] b = new int[1000];
        a.CopyTo(b, 0);
        List<int> temp = b.ToList();
        temp.Sort();
        b = temp.ToArray();

Questo sarebbe un candidato perfetto per entrare in qualche tipo di funzione di supporto, il cui nome indica che sta inizializzando i dati.

    
risposta data 18.03.2012 - 20:53
fonte
4

No, non è OK. Dovresti invece utilizzare TestDataBuilder . Dovresti anche occuparti della leggibilità dei tuoi test: a? 1000? b? Se domani dovessi lavorare sull'implementazione che stai testando, i test sono un ottimo modo per entrare nella logica: scrivi i tuoi test per i tuoi colleghi programmatori, non per il compilatore:)

Ecco l'implementazione dei test, "rinnovata":

/**
* Data your tests will exercice on
*/
public class MyTestData(){
    final int [] values;
    public MyTestData(int sampleSize){
        values = new int[sampleSize];
        //Out of scope of your question : Random IS a depencency you should manage
        Random rand = new Random(DateTime.Now.Millisecond);
        for (int i = 0; i < a.Length; i++)
        {
            a[i] = rand.Next(Int16.MaxValue);
        }
    }
    public int [] values();
        return values;
    }

}

/**
* Data builder, with default value. 
*/
public class MyTestDataBuilder {
    //1000 is actually your sample size : emphasis on the variable name
    private int sampleSize = 1000; //default value of the sample zie
    public MyTestDataBuilder(){
        //nope
    }
    //this is method if you need to test with another sample size
    public MyTestDataBuilder withSampleSizeOf(int size){
        sampleSize=size;
    }

    //call to get an actual MyTestData instance
    public MyTestData build(){
        return new MyTestData(sampleSize);
    }
}

public class MergeSortTest { 

    /**
    * Helper method build your expected data
    */
    private int [] getExpectedData(int [] source){
        int[] expectedData =  Arrays.copyOf(source,source.length);
        Arrays.sort(expectedData);
        return expectedData;
    }
}

//revamped tests method Merge
    public void MergeSortAssertArrayIsSorted(){
        int [] actualData = new MyTestDataBuilder().build();
        int [] expected = getExpectedData(actualData);
        //Don't know what 0 is for. An option, that should have a explicit name for sure :)
        MergeSort merge = new MergeSort();
        merge.mergeSort(actualData,0,actualData.length-1); 
        CollectionAssert.AreEqual(actualData, expected);
    }

 //revamped tests method Insertion
 public void InsertionSortAssertArrayIsSorted()
    {
        int [] actualData = new MyTestDataBuilder().build();
        int [] expected = getExpectedData(actualData);
        InsertionSort merge = new InsertionSort();
        merge.insertionSort(actualData);
        CollectionAssert.AreEqual(actualData, expectedData); 
    }
//another Test, for which very small sample size matter
public void doNotCrashesWithEmptyArray()
    {
        int [] actualData = new MyTestDataBuilder().withSampleSizeOf(0).build();
        int [] expected = getExpectedData(actualData);
        //continue ...
    }
}
    
risposta data 18.03.2012 - 22:25
fonte
2

Ancor più del codice di produzione, il codice di test deve essere ottimizzato per la leggibilità e la manutenibilità, poiché deve essere mantenuto insieme al codice sottoposto a test e letto anche come parte della documentazione. Considera in che modo il codice copiato può rendere più difficile la manutenzione del codice di prova e in che modo può diventare un incentivo a non scrivere test per tutto. Inoltre, non dimenticare che quando scrivi una funzione per ASCIUGARE i tuoi test, dovrebbe anche essere soggetta a test.

    
risposta data 18.03.2012 - 21:54
fonte
2

Duplicare il codice per i test è una facile trappola in cui cadere. Certo è comodo, ma cosa succede se inizi a rifattare il codice di implementazione e tutti i test iniziano a cambiare? Esegui gli stessi rischi che si corrono se hai duplicato il codice di implementazione, in quanto molto probabilmente dovrai anche modificare il codice di prova in molti punti. Tutto ciò comporta una notevole perdita di tempo e un numero crescente di punti di errore che devono essere risolti, il che significa che il costo per mantenere il software diventa inutilmente elevato e quindi riduce il valore aziendale complessivo del software lavorare su.

Considera anche che ciò che è facile da fare nei test sarà facile da fare nell'implementazione. Quando sei sotto pressione per il tempo e sotto stress, le persone tendono a fare affidamento su modelli di comportamento appresi e generalmente cercano e fanno ciò che sembra più facile in quel momento. Quindi, se trovi di tagliare e incollare molto del tuo codice di test, è probabile che tu stia facendo lo stesso nel tuo codice di implementazione, e questa è un'abitudine che vuoi evitare all'inizio della tua carriera, per farti risparmiare molto di difficoltà in seguito quando ti ritrovi a dover mantenere il vecchio codice che hai scritto e che la tua azienda non può necessariamente permettersi di riscrivere.

Come altri hanno già detto, si applica il principio DRY e si cercano opportunità per rifattorizzare eventuali duplicazioni ai metodi helper e alle classi helper, e sì, si dovrebbe fare anche questo nei test per massimizzare il riutilizzo di codice e salvare te stesso affrontando difficoltà con la manutenzione in seguito. Potresti persino trovarti a sviluppare lentamente un'API di test che puoi utilizzare più e più volte, probabilmente anche in più progetti. Certamente è così che sono andate le cose per me negli ultimi anni.

    
risposta data 18.03.2012 - 21:18
fonte

Leggi altre domande sui tag