Il codice duplicato è più tollerabile nei test unitari?


113

Ho rovinato diverse unità di test po 'di tempo fa, quando sono andato attraverso e li refactoring per renderli più SECCO --il intento di ogni test non era più chiaro. Sembra che ci sia un compromesso tra la leggibilità e la manutenibilità dei test. Se lascio il codice duplicato nei test unitari, sono più leggibili, ma se cambio il SUT , dovrò rintracciare e modificare ogni copia del codice duplicato.

Sei d'accordo sul fatto che esista questo compromesso? In tal caso, preferisci che i tuoi test siano leggibili o gestibili?

Risposte:


68

Il codice duplicato è un odore nel codice di unit test tanto quanto in un altro codice. Se hai codice duplicato nei test, diventa più difficile refactoring del codice di implementazione perché hai un numero sproporzionato di test da aggiornare. I test dovrebbero aiutarti a eseguire il refactoring con sicurezza, piuttosto che essere un grande fardello che ostacola il tuo lavoro sul codice da testare.

Se la duplicazione è in fixture impostata, considerare un maggiore utilizzo del setUpmetodo o fornire più (o più flessibile) Creazione Metodi .

Se la duplicazione è nel codice che manipola il SUT, chiediti perché più cosiddetti test "unitari" stanno esercitando la stessa identica funzionalità.

Se la duplicazione è nelle asserzioni, forse hai bisogno di alcune asserzioni personalizzate . Ad esempio, se più test hanno una stringa di asserzioni come:

assertEqual('Joe', person.getFirstName())
assertEqual('Bloggs', person.getLastName())
assertEqual(23, person.getAge())

Allora forse hai bisogno di un unico assertPersonEqualmetodo, in modo da poter scrivere assertPersonEqual(Person('Joe', 'Bloggs', 23), person). (O forse hai semplicemente bisogno di sovraccaricare l'operatore di uguaglianza su Person.)

Come hai detto, è importante che il codice di test sia leggibile. In particolare, è importante che l' intento di un test sia chiaro. Trovo che se molti test sembrano per lo più uguali (ad esempio tre quarti delle linee uguali o praticamente uguali) è difficile individuare e riconoscere le differenze significative senza leggerle e confrontarle attentamente. Quindi trovo che il refactoring per rimuovere la duplicazione aiuti la leggibilità, perché ogni riga di ogni metodo di test è direttamente pertinente allo scopo del test. È molto più utile per il lettore di una combinazione casuale di righe direttamente pertinenti e righe che sono solo standard.

Detto questo, a volte i test esercitano situazioni complesse simili ma comunque significativamente diverse, ed è difficile trovare un buon modo per ridurre la duplicazione. Usa il buon senso: se ritieni che i test siano leggibili e chiarisci il loro intento, e ti senti a tuo agio nel dover forse aggiornare più di un numero teoricamente minimo di test durante il refactoring del codice invocato dai test, accetta l'imperfezione e spostati a qualcosa di più produttivo. Puoi sempre tornare indietro e rifattorizzare i test più tardi, quando l'ispirazione colpisce!


30
"Il codice duplicato è un odore nel codice di unit test tanto quanto in un altro codice." No. "Se hai codice duplicato nei test, diventa più difficile rifattorizzare il codice di implementazione perché hai un numero sproporzionato di test da aggiornare." Ciò accade perché stai testando l'API privata invece dell'API pubblica.

15
Tuttavia, per evitare la duplicazione del codice negli unit test, in genere è necessario introdurre una nuova logica. Non credo che i test unitari debbano contenere logica perché in tal caso avresti bisogno di unit test di unit test.
Petr Peller

@ user11617 definire "API privata" e "API pubblica". A mio avviso, l'api pubblica è l'api visibile al mondo esterno / consumatori di terze parti e con versione esplicita tramite SemVer o simili, qualsiasi altra cosa è privata. Con questa definizione quasi tutti gli unit test stanno testando "API private" e quindi più sensibili alla duplicazione del codice, il che penso sia vero.
KolA

@KolA "Pubblico" non significa consumatori di terze parti: questa non è un'API web. L'API pubblica di una classe si riferisce ai metodi che devono essere utilizzati dal codice client (che in genere non cambia / non dovrebbe cambiare così tanto) - generalmente i metodi "pubblici". L'API privata fa riferimento alla logica e ai metodi utilizzati internamente. Questi non dovrebbero essere accessibili dall'esterno della classe. Questo è uno dei motivi per cui è importante incapsulare correttamente la logica in una classe utilizzando i modificatori di accesso o la convenzione nel linguaggio utilizzato.
Nathan

@ Nathan qualsiasi pacchetto library / dll / nuget ha consumatori di terze parti, non deve essere un'API web. Quello a cui ho fatto riferimento è che è molto comune dichiarare classi e membri pubblici che non dovrebbero essere usati direttamente dai consumatori di librerie (o nella migliore delle ipotesi renderli interni e annotare l'assembly con InternalsVisibleToAttribute) solo per consentire agli unit test di raggiungerli direttamente. Porta a un mucchio di test accoppiati all'implementazione e li rende più un peso che un vantaggio
KolA

186

La leggibilità è più importante per i test. Se un test fallisce, vuoi che il problema sia ovvio. Lo sviluppatore non dovrebbe dover guadare un sacco di codice di test pesantemente scomposto per determinare esattamente cosa ha fallito. Non vuoi che il tuo codice di test diventi così complesso da dover scrivere test unitari.

Tuttavia, l'eliminazione della duplicazione è generalmente una buona cosa, a condizione che non oscuri nulla e l'eliminazione della duplicazione nei test può portare a un'API migliore. Assicurati solo di non andare oltre il punto di rendimenti decrescenti.


xUnit e altri contengono un argomento "messaggio" nelle chiamate di asserzione. È una buona idea inserire frasi significative per consentire agli sviluppatori di trovare rapidamente i risultati dei test non riusciti.
domenica

1
@seand Puoi provare a spiegare cosa sta verificando la tua asserzione, ma quando fallisce e contiene codice oscurato, lo sviluppatore dovrà comunque andare a srotolarlo. IMO È più importante avere un codice che si auto-descriva lì.
IgorK

1
@ Kristopher,? Perché questo post è un wiki della comunità?
Pacerier

@Pacerier non lo so. C'erano regole complicate sulle cose che diventavano automaticamente wiki della comunità.
Kristopher Johnson

Perché la leggibilità dei report è più importante dei test, specialmente quando si esegue l'integrazione o il test end to end, gli scenari potrebbero essere abbastanza complessi da evitare di navigare un po ', va bene trovare l'errore ma ancora una volta per me l'errore nei report dovrebbe spiegare abbastanza bene il problema.
Anirudh

47

Il codice di implementazione e i test sono animali diversi e le regole di factoring si applicano in modo diverso a loro.

Il codice o la struttura duplicati sono sempre un odore nel codice di implementazione. Quando inizi ad avere boilerplate nell'implementazione, devi rivedere le tue astrazioni.

D'altra parte, il codice di test deve mantenere un livello di duplicazione. La duplicazione nel codice di test raggiunge due obiettivi:

  • Mantenere i test disaccoppiati. Un accoppiamento di test eccessivo può rendere difficile la modifica di un singolo test con esito negativo che deve essere aggiornato perché il contratto è stato modificato.
  • Mantenere i test significativi in ​​isolamento. Quando un singolo test fallisce, deve essere ragionevolmente semplice scoprire esattamente cosa sta testando.

Tendo a ignorare la banale duplicazione nel codice di test fintanto che ogni metodo di test rimane più corto di circa 20 righe. Mi piace quando il ritmo di configurazione-esecuzione-verifica è evidente nei metodi di prova.

Quando la duplicazione si insinua nella parte "verifica" dei test, è spesso utile definire metodi di asserzione personalizzati. Naturalmente, quei metodi devono ancora testare una relazione chiaramente identificata che può essere resa evidente nel nome del metodo: assertPegFitsInHole-> buono, assertPegIsGood-> cattivo.

Quando i metodi di test diventano lunghi e ripetitivi, a volte trovo utile definire modelli di test di riempimento che accettano alcuni parametri. Quindi i metodi di test effettivi vengono ridotti a una chiamata al metodo modello con i parametri appropriati.

Per quanto riguarda molte cose nella programmazione e nei test, non esiste una risposta chiara. Devi sviluppare un gusto e il modo migliore per farlo è commettere errori.


8

Sono d'accordo. Il compromesso esiste ma è diverso in luoghi diversi.

È più probabile che esegua il refactoring del codice duplicato per l'impostazione dello stato. Ma è meno probabile che effettui il refactoring della parte del test che esercita effettivamente il codice. Detto questo, se l'esercizio del codice richiede sempre diverse righe di codice, potrei pensare che sia un odore e refactoring del codice effettivo in prova. E questo migliorerà la leggibilità e la manutenibilità sia del codice che dei test.


Penso che questa sia una buona idea. Se si hanno molti duplicati, vedere se è possibile eseguire il refactoring per creare un "dispositivo di test" comune in cui possono essere eseguiti molti test. Ciò eliminerà il codice di configurazione / smontaggio duplicato.
Programmatore fuorilegge

8

È possibile ridurre la ripetizione utilizzando diversi tipi di metodi di utilità di test .

Sono più tollerante nei confronti della ripetizione nel codice di test che nel codice di produzione, ma a volte ne sono stato frustrato. Quando modifichi il design di una classe e devi tornare indietro e modificare 10 diversi metodi di test che eseguono tutti gli stessi passaggi di configurazione, è frustrante.


6

Jay Campi coniato la frase che "DSL deve essere umido, non asciutto", dove umido mezzi descrittivo e frasi significative . Penso che lo stesso valga anche per i test. Ovviamente, troppe duplicazioni sono un male. Ma rimuovere la duplicazione a tutti i costi è anche peggio. I test dovrebbero fungere da specifiche rivelatrici di intenti. Se, ad esempio, si specifica la stessa caratteristica da diverse angolazioni, è prevedibile una certa quantità di duplicazione.


3

ADORO rspec per questo:

Ha 2 cose per aiutare:

  • gruppi di esempio condivisi per testare comportamenti comuni.
    puoi definire una serie di test, quindi "includere" quella serie nei tuoi test reali.

  • contesti annidati.
    puoi essenzialmente avere un metodo di "configurazione" e "smontaggio" per un sottoinsieme specifico dei tuoi test, non solo per tutti nella classe.

Prima che .NET / Java / altri framework di test adottino questi metodi, meglio è (o potresti usare IronRuby o JRuby per scrivere i tuoi test, che personalmente ritengo sia l'opzione migliore)


3

Ritengo che il codice di test richieda un livello di ingegneria simile a quello normalmente applicato al codice di produzione. Sicuramente ci possono essere argomenti a favore della leggibilità e sono d'accordo che è importante.

Nella mia esperienza, tuttavia, trovo che i test ben fattorizzati siano più facili da leggere e comprendere. Se ci sono 5 test che sembrano tutti uguali tranne per una variabile che è cambiata e l'asserzione alla fine, può essere molto difficile trovare quale sia quel singolo elemento diverso. Allo stesso modo, se viene scomposto in modo che sia visibile solo la variabile che sta cambiando e l'asserzione, è facile capire immediatamente cosa sta facendo il test.

Trovare il giusto livello di astrazione durante i test può essere difficile e credo che valga la pena farlo.


2

Non penso che ci sia una relazione tra codice più duplicato e leggibile. Penso che il tuo codice di prova dovrebbe essere buono come il tuo altro codice. Il codice che non si ripete è più leggibile del codice duplicato se eseguito correttamente.


2

Idealmente, gli unit test non dovrebbero cambiare molto una volta scritti, quindi preferirei la leggibilità.

Fare in modo che gli unit test siano il più discreti possibile aiuta anche a mantenere i test focalizzati sulla funzionalità specifica a cui sono destinati.

Detto questo, tendo a riutilizzare alcuni pezzi di codice che finisco per usare più e più volte, come il codice di installazione che è esattamente lo stesso in una serie di test.


2

"li ha rifattorizzati per renderli più ASCIUTTI - l'intento di ogni test non era più chiaro"

Sembra che tu abbia avuto problemi con il refactoring. Sto solo indovinando, ma se è risultato meno chiaro, non significa che hai ancora più lavoro da fare in modo da avere test ragionevolmente eleganti che siano perfettamente chiari?

Ecco perché i test sono una sottoclasse di UnitTest, quindi puoi progettare buone suite di test che siano corrette, facili da convalidare e chiare.

In passato avevamo strumenti di test che utilizzavano linguaggi di programmazione diversi. Era difficile (o impossibile) progettare test piacevoli e facili da lavorare.

Hai tutta la potenza di - qualunque lingua tu stia utilizzando - Python, Java, C # - quindi usa bene quel linguaggio. È possibile ottenere un codice di prova di bell'aspetto che sia chiaro e non troppo ridondante. Non ci sono compromessi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.