Come testare quando organizzare i dati è troppo macchinoso?

18

Sto scrivendo un parser e, come parte di ciò, ho una classe Expander che "espande" una singola istruzione complessa in più semplici istruzioni. Ad esempio, espanderebbe questo:

x = 2 + 3 * a

in:

tmp1 = 3 * a
x = 2 + tmp1

Ora sto pensando a come testare questa classe, in particolare come organizzare i test. Potrei creare manualmente l'albero della sintassi di input:

var input = new AssignStatement(
    new Variable("x"),
    new BinaryExpression(
        new Constant(2),
        BinaryOperator.Plus,
        new BinaryExpression(new Constant(3), BinaryOperator.Multiply, new Variable("a"))));

Oppure potrei scriverlo come una stringa e analizzarlo:

var input = new Parser().ParseStatement("x = 2 + 3 * a");

La seconda opzione è molto più semplice, più breve e leggibile. Ma introduce anche una deneglianza su Parser , il che significa che un errore in Parser potrebbe fallire questo test. Quindi, il test smetterebbe di essere un test unitario di Expander , e credo che tecnicamente diventi un test di integrazione di Parser e Expander .

La mia domanda è: è corretto affidarsi principalmente (o completamente) a questo tipo di test di integrazione per testare questa classe Expander ?

    
posta svick 04.12.2014 - 15:52
fonte

4 risposte

26

Ti troverai a scrivere molti più test, un comportamento molto più complicato, interessante e utile, se puoi farlo semplicemente. Quindi l'opzione che coinvolge

var input = new Parser().ParseStatement("x = 2 + 3 * a");

è abbastanza valido. Dipende da un altro componente. Ma tutto dipende da dozzine di altri componenti. Se prendi in giro qualcosa con un centimetro della sua vita, probabilmente stai dipendendo da molte funzioni di simulazione e test di prova.

Gli sviluppatori a volte si concentrano eccessivamente sulla purezza dei loro test unitari , o sviluppando test unitari e unit test solo , senza alcun modulo, integrazione, stress o altri tipi di test. Tutte queste forme sono valide e utili, e sono tutte responsabilità degli sviluppatori - non solo Q / A o il personale operativo più avanti nella pipeline.

Un approccio che ho usato è quello di iniziare con queste esecuzioni di livello più alto, quindi utilizzare i dati prodotti da esse per costruire l'espressione del test a lungo termine, con il minimo comune denominatore. Per esempio. quando esegui il dump della struttura dati dalla input prodotta in precedenza, puoi facilmente costruire:

var input = new AssignStatement(
    new Variable("x"),
    new BinaryExpression(
        new Constant(2),
        BinaryOperator.Plus,
        new BinaryExpression(new Constant(3), BinaryOperator.Multiply, new Variable("a"))));

tipo di test che verifica al livello più basso. In questo modo ottieni un bel mix: una manciata di test primitivi di base (pur unit test), ma non ho trascorso una settimana a scrivere test a quel livello primitivo. Questo ti dà la risorsa di tempo necessaria per scrivere molti più test atomici leggermente meno usando Parser come helper. Risultato finale: più test, più copertura, più corner e altri casi interessanti, codice migliore e garanzia di qualità più elevata.

    
risposta data 04.12.2014 - 16:21
fonte
6

Certo che va bene!

Hai sempre bisogno di test funzionali / di integrazione che esercitino il percorso completo del codice. E il percorso del codice completo in questo caso significa che include la valutazione del codice generato. Questo è il tuo test che l'analisi di x = 2 + 3 * a produce codice che, se eseguito con a = 5 , imposta x su 17 e se eseguito con a = -2 imposta x su -4 .

Al di sotto di questo, dovresti fare dei test unitari per i bit più piccoli purché in realtà aiuti a eseguire il debug del codice . I test a grana più fine avrai, maggiore è la probabilità che qualsiasi modifica al codice debba cambiare anche il test, perché l'interfaccia interna cambia. Tali test hanno poco valore a lungo termine e aggiungono interventi di manutenzione. Quindi c'è un punto di rendimenti decrescenti e dovresti fermarti prima di questo.

    
risposta data 04.12.2014 - 16:25
fonte
4

I test unitari ti consentono di individuare gli elementi specifici che si rompono e il codice nel punto in cui sono stati interrotti. Quindi sono buoni per i test a grana molto fine. I buoni test unitari aiuteranno a ridurre i tempi di debug.

Tuttavia, dalla mia esperienza, i test di unità raramente sono sufficienti per verificare effettivamente il corretto funzionamento. Quindi i test di integrazione sono anche utili per verificare una catena o una sequenza di operazioni. I test di integrazione ti aiutano a superare i test funzionali. Come hai sottolineato, a causa della complessità dei test di integrazione, è più difficile trovare il punto specifico nel codice in cui si interrompe il test. Ha anche un po 'più fragilità in quanto i guasti in qualsiasi punto della catena causeranno il fallimento del test. Avrai comunque quella catena nel codice di produzione, quindi testare la catena effettiva è comunque utile.

Idealmente avresti entrambi, ma in ogni caso, generalmente un test automatico è meglio che non avere nessun test.

    
risposta data 04.12.2014 - 16:24
fonte
0

Esegui molti test sul parser e mentre il parser supera i test, salva tali output in un file per prendere in giro il parser e testare l'altro componente.

    
risposta data 04.08.2016 - 14:07
fonte

Leggi altre domande sui tag