Quanto sono profondi i tuoi unit test?


88

La cosa che ho scoperto su TDD è che ci vuole tempo per impostare i tuoi test ed essendo naturalmente pigro voglio sempre scrivere il minor numero di codice possibile. La prima cosa che mi sembra di fare è testare che il mio costruttore abbia impostato tutte le proprietà, ma è eccessivo?

La mia domanda è: a quale livello di granularità scrivi i tuoi unit test?

..e c'è un caso di test troppo?

Risposte:


221

Vengo pagato per codice che funziona, non per i test, quindi la mia filosofia è testare il meno possibile per raggiungere un determinato livello di fiducia (sospetto che questo livello di fiducia sia alto rispetto agli standard del settore, ma potrebbe essere solo arroganza) . Se in genere non commetto una sorta di errore (come impostare le variabili sbagliate in un costruttore), non lo test. Tendo a dare un senso agli errori di test, quindi sono molto attento quando ho logica con condizionali complicati. Quando creo codice in un team, modifico la mia strategia per testare attentamente il codice che, collettivamente, tendiamo a sbagliare.

Persone diverse avranno strategie di test diverse basate su questa filosofia, ma mi sembra ragionevole dato lo stato di comprensione immaturo di come i test possono adattarsi al meglio nel ciclo interno della codifica. Tra dieci o vent'anni probabilmente avremo una teoria più universale su quali test scrivere, quali test non scrivere e come distinguere. Nel frattempo, la sperimentazione sembra in ordine.


40
Il mondo non pensa che Kent Beck lo direbbe! Ci sono legioni di sviluppatori che perseguono diligentemente una copertura del 100% perché pensano che sia quello che farebbe Kent Beck! Ho detto a molti che hai detto, nel tuo libro su XP, che non sempre aderisci al Test First religiosamente. Ma anche io sono sorpreso.
Charlie Flowers,

6
In realtà non sono d'accordo, perché il codice che uno sviluppatore produce non è il suo, e il prossimo sprint qualcun altro lo cambierà e commetterà errori che "sai di non avere". Anche TDD pensi prima ai test. Quindi, se esegui TDD assumendo di testare parte del codice, lo stai facendo male
Ricardo Rodrigues

2
Non mi interessa la copertura. Sono molto interessato alla frequenza con cui il signor Beck esegue il commit del codice che non è stato scritto in risposta a un test non riuscito.
sheldonh

1
@RicardoRodrigues, non puoi scrivere test per coprire il codice che altre persone scriveranno in seguito. Questa è la loro responsabilità.
Kief

2
Non è quello che ho scritto, letto attentamente; Ho scritto che se scrivi test per coprire solo una parte del TUO codice, lasciando parti scoperte dove "sapevi di non commettere errori" e quelle parti vengono cambiate e non hanno test adeguati, hai un problema proprio lì, e questo non è affatto TDD.
Ricardo Rodrigues

20

Scrivi unit test per le cose che ti aspetti di rompere e per i casi limite. Successivamente, i casi di test dovrebbero essere aggiunti man mano che arrivano le segnalazioni di bug, prima di scrivere la correzione del bug. Lo sviluppatore può quindi essere certo che:

  1. Il bug è stato risolto;
  2. Il bug non riapparirà.

Secondo il commento allegato - Immagino che questo approccio alla scrittura di unit test possa causare problemi, se molti bug vengono, nel tempo, scoperti in una data classe. Questo è probabilmente il punto in cui la discrezione è utile, aggiungendo unit test solo per i bug che è probabile che si ripresentino, o dove il loro ripetersi potrebbe causare seri problemi. Ho scoperto che una misura del test di integrazione nei test unitari può essere utile in questi scenari: il test del codice più in alto i codepath possono coprire i codepath più in basso.


Con la quantità di bug che scrivo, questo può diventare un anti pattern. Con centinaia di test sul codice in cui le cose si sono interrotte, questo può significare che i tuoi test diventano illeggibili e quando arriva il momento di riscrivere quei test può diventare un sovraccarico.
Johnno Nolan

@ JohnNolan: la leggibilità del test è così importante? IMHO non lo è, almeno per questi test di regressione specifici per i bug. Se stai riscrivendo frequentemente i test, potresti testare a un livello troppo basso - idealmente le tue interfacce dovrebbero rimanere relativamente stabili anche se le tue implementazioni cambiano e dovresti testare a livello di interfaccia (anche se mi rendo conto che il mondo reale spesso non lo è ti piace ...: - /) Se le tue interfacce cambiano in modo sostanziale, preferirei scartare la maggior parte o tutti questi test specifici dei bug piuttosto che riscriverli.
j_random_hacker

@j_random_hacker Sì, ovviamente la leggibilità è importante. I test sono una forma di documentazione e sono importanti quanto il codice di produzione. Sono d'accordo che scartare i test per i cambiamenti importanti è una buona cosa (tm) e che i test dovrebbero essere eseguiti a livello di interfaccia.
Johnno Nolan

19

Tutto dovrebbe essere reso il più semplice possibile, ma non più semplice. - A. Einstein

Una delle cose più fraintese su TDD è la prima parola in esso. Test. Ecco perché è arrivato BDD. Perché le persone non capivano davvero che la prima D era quella importante, ovvero Driven. Tendiamo tutti a pensare un po 'troppo ai test e un po' troppo alla guida del design. E immagino che questa sia una risposta vaga alla tua domanda, ma dovresti probabilmente considerare come guidare il tuo codice, invece di quello che stai effettivamente testando; questo è qualcosa in cui uno strumento di copertura può aiutarti. Il design è una questione molto più grande e problematica.


Sì, è vago ... Questo significa che, dato che un costruttore non fa parte del comportamento, non dovremmo testarlo. Ma dovrei testare MyClass.DoSomething ()?
Johnno Nolan,

Bene, dipende da: P ... un test di costruzione è spesso un buon inizio quando si prova a testare il codice legacy. Ma probabilmente (nella maggior parte dei casi) lascerei fuori un test di costruzione, quando inizierei a progettare qualcosa da zero.
kitofr

È uno sviluppo guidato, non un design guidato. Significa, ottenere una linea di base di lavoro, scrivere test per verificare la funzionalità, andare avanti con lo sviluppo. Scrivo quasi sempre i miei test subito prima di fattorizzare del codice per la prima volta.
Evan Plaice

Direi che l'ultima D, Design, è la parola che le persone dimenticano, perdendo così la concentrazione. Nella progettazione basata sui test, scrivi codice in risposta a test non riusciti. Se stai realizzando una progettazione basata su test, quanto codice non testato ti ritroverai?
sheldonh

15

A coloro che propongono di testare "tutto": rendersi conto che "testare completamente" un metodo come int square(int x)richiede circa 4 miliardi di casi di test in linguaggi comuni e ambienti tipici.

In realtà, è anche peggio di quello: un metodo void setX(int newX)è inoltre tenuto non alterare i valori di tutti gli altri membri oltre x- stai testando che obj.y, obj.zecc tutti rimangono invariati dopo aver chiamato obj.setX(42);?

È solo pratico testare un sottoinsieme di "tutto". Una volta accettato questo, diventa più appetibile considerare di non testare un comportamento incredibilmente semplice. Ogni programmatore ha una distribuzione di probabilità delle posizioni dei bug; l'approccio intelligente consiste nel concentrare le proprie energie sui test delle regioni in cui si stima che la probabilità di bug sia alta.


9

La risposta classica è "prova tutto ciò che potrebbe rompersi". Lo interpreto nel senso che testare setter e getter che non fanno nulla tranne set o get è probabilmente troppo test, non c'è bisogno di prendere tempo. A meno che il tuo IDE non li scriva per te, potresti farlo anche tu.

Se il tuo costruttore non imposta le proprietà potrebbe portare a errori in un secondo momento, testare che siano impostate non è eccessivo.


sì e questo è un vincolo per una classe con molte proprietà e molti costitutori.
Johnno Nolan,

Più un problema è banale (come dimenticare di inizializzare un membro a zero), più tempo ci vorrà per eseguirne il debug.
Lev

5

Scrivo test per coprire i presupposti delle classi che scriverò. I test applicano i requisiti. In sostanza, se x non può mai essere 3, ad esempio, mi assicurerò che esista un test che soddisfi tale requisito.

Invariabilmente, se non scrivo un test per coprire una condizione, apparirà più tardi durante i test "umani". Ne scriverò sicuramente uno allora, ma preferisco prenderli presto. Penso che il punto sia che il test è noioso (forse) ma necessario. Scrivo abbastanza test per essere completo, ma non di più.


5

Parte del problema con saltare semplici test ora è che in futuro il refactoring potrebbe rendere quella semplice proprietà molto complicata con molta logica. Penso che l'idea migliore sia che puoi usare i test per verificare i requisiti per il modulo. Se quando superi X dovresti riavere Y, allora è quello che vuoi testare. Quindi, quando modifichi il codice in un secondo momento, puoi verificare che X ti dia Y e puoi aggiungere un test per A ti dà B, quando quel requisito viene aggiunto in seguito.

Ho scoperto che il tempo speso durante lo sviluppo iniziale dei test di scrittura ripaga nella prima o seconda correzione di bug. La capacità di raccogliere codice che non hai esaminato in 3 mesi ed essere ragionevolmente sicuro che la tua correzione copra tutti i casi e "probabilmente" non rompe nulla è estremamente preziosa. Scoprirai anche che i test unitari aiuteranno a valutare i bug ben oltre la traccia dello stack, ecc. Vedere come funzionano e falliscono i singoli pezzi dell'app fornisce una visione approfondita del motivo per cui funzionano o falliscono nel complesso.


4

Nella maggior parte dei casi, direi che, se c'è della logica, provala. Ciò include costruttori e proprietà, soprattutto quando più di una cosa viene impostata nella proprietà.

Riguardo a troppi test, è discutibile. Alcuni direbbero che tutto dovrebbe essere testato per la robustezza, altri dicono che per un test efficiente, dovrebbero essere testati solo le cose che potrebbero rompersi (cioè la logica).

Mi spingerei di più verso il secondo campo, solo per esperienza personale, ma se qualcuno decidesse di testare tutto, non direi che era troppo ... forse un po 'eccessivo per me, ma non troppo per loro.

Quindi, No, direi che non esiste una cosa come "troppi" test in senso generale, solo per individui.


3

Test Driven Development significa che interrompi la codifica quando tutti i tuoi test vengono superati.

Se non hai un test per una proprietà, perché dovresti implementarlo? Se non testate / definite il comportamento previsto in caso di assegnazione "illegale", cosa dovrebbe fare la proprietà?

Pertanto sono assolutamente favorevole a testare ogni comportamento che una classe dovrebbe mostrare. Comprese le proprietà "primitive".

Per rendere più facile questo test, ho creato una semplice NUnit TestFixtureche fornisce punti di estensione per l'impostazione / l'acquisizione del valore e prende elenchi di valori validi e non validi e dispone di un singolo test per verificare se la proprietà funziona correttamente. Il test di una singola proprietà potrebbe essere simile a questo:

[TestFixture]
public class Test_MyObject_SomeProperty : PropertyTest<int>
{

    private MyObject obj = null;

    public override void SetUp() { obj = new MyObject(); }
    public override void TearDown() { obj = null; }

    public override int Get() { return obj.SomeProperty; }
    public override Set(int value) { obj.SomeProperty = value; }

    public override IEnumerable<int> SomeValidValues() { return new List() { 1,3,5,7 }; }
    public override IEnumerable<int> SomeInvalidValues() { return new List() { 2,4,6 }; }

}

Utilizzando lambda e attributi questo potrebbe anche essere scritto in modo più compatto. Ho capito che MBUnit ha anche un supporto nativo per cose del genere. Il punto però è che il codice precedente cattura l'intento della proprietà.

PS: Probabilmente il PropertyTest dovrebbe anche avere un modo per verificare che le altre proprietà dell'oggetto non siano cambiate. Hmm .. torniamo al tavolo da disegno.


Sono andato a una presentazione su mbUnit. Sembra grandioso.
Johnno Nolan

Ma David, lascia che ti chieda: sei rimasto sorpreso dalla risposta di Kent Beck sopra? La sua risposta ti fa chiedere se dovresti ripensare il tuo approccio? Non perché qualcuno abbia "risposte dall'alto", ovviamente. Ma Kent è considerato innanzitutto uno dei principali sostenitori del test. Penny per i tuoi pensieri!
Charlie Flowers

@Charly: la risposta di Kent è molto pragmatica. Sto "solo" lavorando a un progetto in cui integrerò codice da varie fonti e mi piacerebbe fornire un livello di sicurezza molto alto.
David Schmitt,

Detto questo, mi sforzo di avere test più semplici del codice testato e questo livello di dettaglio potrebbe valerne la pena solo nei test di integrazione in cui tutti i generatori, i moduli, le regole di business e i validatori si uniscono.
David Schmitt,

1

Faccio unit test per raggiungere la massima copertura possibile. Se non riesco a raggiungere un codice, eseguo il refactoring fino a quando la copertura non è il più completa possibile

Dopo aver finito il test di scrittura accecante, di solito scrivo un caso di test riproducendo ogni bug

Sono abituato a separare tra test del codice e test di integrazione. Durante i test di integrazione (che sono anche test di unità ma su gruppi di componenti, quindi non esattamente a cosa servono i test di unità) testerò per implementare correttamente i requisiti.


1

Quindi più guido la mia programmazione scrivendo test, meno mi preoccupo del livello di granualità dei test. Guardando indietro sembra che stia facendo la cosa più semplice possibile per raggiungere il mio obiettivo di convalidare il comportamento . Ciò significa che sto generando uno strato di fiducia che il mio codice stia facendo ciò che chiedo di fare, tuttavia ciò non è considerato come una garanzia assoluta che il mio codice sia privo di bug. Ritengo che il giusto equilibrio sia testare il comportamento standard e forse uno o due casi limite, quindi passare alla parte successiva del mio progetto.

Accetto che questo non coprirà tutti i bug e utilizzo altri metodi di test tradizionali per catturarli.


0

In generale, inizio in piccolo, con input e output che so devono funzionare. Quindi, mentre correggo i bug, aggiungo più test per assicurarmi che le cose che ho risolto siano testate. È organico e funziona bene per me.

Puoi testare troppo? Probabilmente, ma probabilmente è meglio sbagliare sul lato della cautela in generale, anche se dipenderà da quanto sia critica la tua applicazione.


0

Penso che tu debba testare tutto nel tuo "nucleo" della tua logica di business. Getter e anche Setter perché potrebbero accettare un valore negativo o un valore nullo che potresti non voler accettare. Se hai tempo (dipende sempre dal tuo capo) è bene testare altre logiche di business e tutti i controller che chiamano questi oggetti (si passa dallo unit test al test di integrazione lentamente).


0

Non collaudo semplici metodi setter / getter che non hanno effetti collaterali. Ma eseguo un test unitario con ogni altro metodo pubblico. Cerco di creare test per tutte le condizioni al contorno nei miei algoritmi e controllo la copertura dei miei test unitari.

È un sacco di lavoro ma penso che ne valga la pena. Preferirei scrivere codice (anche testare il codice) piuttosto che passare da un codice all'altro in un debugger. Trovo che il ciclo code-build-deploy-debug richieda molto tempo e più esaurienti sono gli unit test che ho integrato nella mia build, meno tempo trascorro nel ciclo di code-build-deploy-debug.

Non hai detto perché stai codificando anche l'architettura. Ma per Java io uso Maven 2 , JUnit , DBUnit , Cobertura , e EasyMock .


Non ho detto che è una domanda abbastanza indipendente dalla lingua.
Johnno Nolan,

Il test unitario in TDD non solo ti copre mentre scrivi il codice, ma protegge anche dalla persona che eredita il tuo codice e quindi pensa che abbia senso formattare un valore all'interno del getter!
Paxic

0

Più leggo su di esso più penso che alcuni test unitari siano proprio come alcuni schemi: Un odore di lingue insufficienti.

Quando devi verificare se il tuo banale getter restituisce effettivamente il valore corretto, è perché potresti mescolare il nome del getter e il nome della variabile membro. Inserisci 'attr_reader: name' di ruby, e questo non può più accadere. Semplicemente non possibile in Java.

Se il tuo getter non diventa mai banale, puoi comunque aggiungere un test per questo.


Sono d'accordo che testare un getter sia banale. Tuttavia potrei essere così stupido da dimenticarmi di impostarlo all'interno di un costruttore. Pertanto è necessario un test. I miei pensieri sono cambiati da quando ho posto la domanda. Vedi la mia risposta stackoverflow.com/questions/153234/how-deep-are-your-unit-tests/…
Johnno Nolan

1
In realtà, direi che in qualche modo gli unit test nel loro insieme sono l'odore di un problema linguistico. Le lingue che supportano i contratti (condizioni pre / post sui metodi) come Eiffel, necessitano ancora di alcuni unit test, ma ne richiedono meno. In pratica, anche i contratti semplici rendono davvero facile individuare i bug: quando il contratto di un metodo si interrompe, il bug di solito è in quel metodo.
Damien Pollet

@ Damien: Forse unit test e contratti sono davvero la stessa cosa sotto mentite spoglie? Quello che voglio dire è che un linguaggio che "supporta" i contratti fondamentalmente semplifica la scrittura di frammenti di codice - test - che vengono (facoltativamente) eseguiti prima e dopo altri frammenti di codice, corretto? Se la sua grammatica è abbastanza semplice, un linguaggio che non supporta nativamente i contratti può essere facilmente esteso per supportarli scrivendo un preprocessore, giusto? O ci sono cose che un approccio (contratti o unit test) può fare e che l'altro non può fare?
j_random_hacker

0

Testa il codice sorgente che ti preoccupa.

Non è utile testare porzioni di codice in cui si è molto fiduciosi, purché non si commettano errori.

Prova le correzioni dei bug, in modo che sia la prima e l'ultima volta che risolvi un bug.

Esegui il test per acquisire confidenza con parti di codice oscure, in modo da creare conoscenza.

Test prima del refactoring pesante e medio, in modo da non interrompere le funzionalità esistenti.


0

Questa risposta è più utile per capire quanti unit test utilizzare per un dato metodo che sai di voler eseguire unit test a causa della sua criticità / importanza. Utilizzando la tecnica Basis Path Testing di McCabe, è possibile eseguire le seguenti operazioni per ottenere quantitativamente una maggiore sicurezza della copertura del codice rispetto alla semplice "copertura delle istruzioni" o "copertura delle filiali":

  1. Determina il valore di complessità ciclomatica del tuo metodo che desideri testare (ad esempio Visual Studio 2010 Ultimate può calcolarlo per te con strumenti di analisi statica; altrimenti, puoi calcolarlo a mano tramite il metodo flowgraph - http://users.csc. calpoly.edu/~jdalbey/206/Lectures/BasisPathTutorial/index.html )
  2. Elenca il set di base dei percorsi indipendenti che fluiscono attraverso il tuo metodo - vedi il link sopra per un esempio di diagramma di flusso
  3. Preparare unit test per ogni percorso di base indipendente determinato nel passaggio 2

Lo farai per ogni metodo? Sul serio?
Kristopher Johnson
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.