Come metodo di test unitario che restituisce una raccolta evitando logiche nel test

14

Sto testando un metodo che genera una collezione di oggetti dati. Voglio verificare che le proprietà degli oggetti siano impostate correttamente. Alcune proprietà saranno impostate sulla stessa cosa; altri saranno impostati su un valore che dipende dalla loro posizione nella raccolta. Il modo naturale per farlo sembra essere un loop. Tuttavia, Roy Osherove raccomanda vivamente di non utilizzare la logica nei test unitari ( Art of Unit Testing , 178). Dice:

A test that contains logic is usually testing more than one thing at a time, whicfh isn't recommended, because the test is less readable and more fragile. But test logic also adds complexity that may contain a hidden bug.

Tests should, as a general rule, be a series of method calls with no control flows, not even try-catch, and with assert calls.

Tuttavia, non riesco a vedere nulla di sbagliato nella mia progettazione (in che altro modo generi un elenco di oggetti dati, alcuni dei cui valori dipendono da dove si trovano nella sequenza? -non è possibile generarli e testarli esattamente separatamente). C'è qualcosa di non test-friendly con il mio design? O mi sto dedicando troppo rigidamente all'insegnamento di Osherove? O c'è qualche magia di prova dell'unità segreta che non conosco che aggira questo problema? (Sto scrivendo in C # / VS2010 / NUnit, ma cerco le risposte indipendenti dalla lingua se possibile.)

    
posta Kazark 07.05.2013 - 18:58
fonte

3 risposte

13

TL; DR:

  • Scrivi il test
  • Se il test fa troppo, il codice potrebbe fare troppo.
  • Potrebbe non essere un test unitario (ma non un test negativo).

La prima cosa da testare è che il dogma è inutile. Mi piace leggere The Way of Testivus che evidenzia alcuni problemi con il dogma in modo spensierato.

Write the test that needs to be written.

Se il test deve essere scritto in qualche modo, scrivilo in questo modo. Tentare di forzare il test in un layout di prova idealizzato o non averlo affatto non è una buona cosa. Avere un test oggi che testare è meglio che avere un test "perfetto" qualche giorno dopo.

Indicherò anche il bit del test brutto:

When the code is ugly, the tests may be ugly.

You don’t like to write ugly tests, but ugly code needs testing the most.

Don’t let ugly code stop you from writing tests, but let ugly code stop you from writing more of it.

Questi possono essere considerati truismi per coloro che hanno seguito per lungo tempo ... e sono semplicemente radicati nel modo di pensare e scrivere test. Per le persone che non sono state e stanno cercando di arrivare a quel punto, i promemoria possono essere utili (trovo anche che rileggerli mi aiuta a evitare di essere bloccato in qualche dogma).

Considera che quando scrivi un brutto test, se il codice può essere un'indicazione che il codice sta cercando di fare troppo. Se il codice che stai testando è troppo complesso per essere correttamente esercitato scrivendo un semplice test, potresti prendere in considerazione la possibilità di suddividere il codice in parti più piccole che possono essere testate con i test più semplici. Non si dovrebbe scrivere un test unitario che faccia tutto (potrebbe non essere un test unit quindi). Proprio come gli "oggetti divini" sono cattivi, anche i "test di unità divina" sono cattivi e dovrebbero essere indicazioni per tornare indietro e guardare di nuovo il codice.

dovresti essere in grado di esercitare tutto il codice con una copertura ragionevole attraverso tali semplici test. Test che fanno più end-end test che trattano domande più grandi ("Ho questo oggetto, marshalled in xml, inviato al servizio web, attraverso le regole, back-out e unmarshalled") è un test eccellente - ma certamente non è 't a unit test (e rientra nel campo del testing di integrazione - anche se ha eseguito servizi di simulazione che chiama e personalizzato nei database di memoria per eseguire il test). Può ancora usare il framework XUnit per i test, ma il framework di test non lo rende un test unitario.

    
risposta data 07.05.2013 - 20:36
fonte
6

Sto aggiungendo una nuova risposta perché la mia prospettiva è diversa da quando ho scritto la domanda e la risposta originali; non ha senso inserirli in uno solo.

Ho detto nella domanda originale

However, I can't see anything wrong with my design (how else do you generate a list of data objects, some of whose values are depended on where in the sequence they are?—can't exactly generate and test them separately)

È qui che ho sbagliato. Dopo aver fatto la programmazione funzionale per l'ultimo anno, ora mi rendo conto che avevo solo bisogno di un'operazione di raccolta con un accumulatore. Quindi potrei scrivere la mia funzione come una funzione pura che ha operato su una cosa e utilizzare alcune funzioni della libreria standard per applicarla alla collezione.

Quindi la mia nuova risposta è: usa le tecniche di programmazione funzionale e eviterai questo problema per la maggior parte del tempo. Puoi scrivere le tue funzioni per operare su singole cose e applicarle solo a raccolte di cose all'ultimo momento. Ma se sono puri puoi testarli senza riferimento alle collezioni.

Per una logica più complessa, poggia su test basati sulle proprietà . Quando hanno una logica, dovrebbero essere inferiori e inversi alla logica del codice sotto test, e ogni test verifica molto più di un test unitario basato sui casi che vale la piccola quantità di logica.

Soprattutto tieni sempre i tuoi tipi . Ottieni i tipi più forti che puoi e usali a tuo vantaggio. Ciò ridurrà il numero di test che devi scrivere in primo luogo.

    
risposta data 03.09.2015 - 19:59
fonte
4

Non provare a testare troppe cose contemporaneamente. Ciascuna delle proprietà di ciascun oggetto dati nella raccolta è troppo per un test. Invece, consiglio:

  1. Se la raccolta è a lunghezza fissa, scrivi un test unitario per convalidare la lunghezza. Se è di lunghezza variabile, scrivi diversi test per le lunghezze che caratterizzeranno il suo comportamento (ad es. 0, 1, 3, 10). In ogni caso, non convalidare le proprietà in questi test.
  2. Scrivi un test unitario per convalidare ciascuna delle proprietà. Se la raccolta è fissa, lunga e corta, basta asserire contro una proprietà di ciascuno degli elementi per ciascun test. Se è di lunghezza fissa ma lunga, scegliere un campione rappresentativo ma di dimensioni ridotte per far valere una proprietà ciascuna. Se è di lunghezza variabile, genera una raccolta relativamente breve ma rappresentativa (vale a dire forse tre elementi) e asserisce contro una proprietà di ciascuno.

Fare in questo modo rende i test abbastanza piccoli da escludere loop che non sembrano dolorosi. Esempio C # / Unit, dato metodo sotto test ICollection<Foo> generateFoos(uint numberOfFoos) :

[Test]
void generate_zero_foos_returns_empty_list() { ... }
void generate_one_foo_returns_list_of_one_foo() { ... }
void generate_three_foos_returns_list_of_three_foos() { ... }
void generated_foos_have_sequential_ID()
{
    var foos = generateFoos(3).GetEnumerable();
    foos.MoveNext();
    Assert.AreEqual("ID1", foos.Current.id);
    foos.MoveNext();
    Assert.AreEqual("ID2", foos.Current.id);
    foos.MoveNext();
    Assert.AreEqual("ID3", foos.Current.id);
}
void generated_foos_have_bar()
{
    var foos = generateFoos(3).GetEnumerable();
    foos.MoveNext();
    Assert.AreEqual("baz", foos.Current.bar);
    foos.MoveNext();
    Assert.AreEqual("baz", foos.Current.bar);
    foos.MoveNext();
    Assert.AreEqual("baz", foos.Current.bar);
}

Se sei abituato al paradigma del "test dell'unità piatta" (nessuna struttura / logica nidificata), questi test sembrano abbastanza puliti. Pertanto, la logica viene evitata nei test identificando il problema originale come se si provassero a testare troppe proprietà in una volta, piuttosto che in mancanza di loop.

    
risposta data 07.05.2013 - 21:21
fonte

Leggi altre domande sui tag