Cosa si intende in "unità" in test di unità

9

Come capisco in teoria sotto "unità" le persone significano metodo (in OOP). Ma in pratica i test che verificano alcuni metodi isolati sono test di comportamento molto fragili (verificando non il risultato ma il fatto che sia stato chiamato un metodo di dipendenza). Quindi vedo un sacco di persone che per unità comprendono una piccola serie di classi strettamente correlate. In questo caso, solo le dipendenze esterne vengono prese in giro / bloccate e per le dipendenze all'interno dell'unità vengono utilizzate le implementazioni reali. In questo caso ci sono più stati, significativi (secondo le specifiche) e test non fragili. Quindi la domanda è: cosa ne pensi di questi approcci ed è valido chiamare il secondo test dell'unità di approccio o potrebbe essere una sorta di test di integrazione di basso livello?

Se vedi alcune considerazioni specifiche sull'applicazione del TDD attraverso uno di questi modi di test, ti sarei grato per i tuoi pensieri.

    
posta SiberianGuy 14.08.2011 - 20:39
fonte

4 risposte

11

Originariamente TDD proveniva dal movimento agile, in cui i test venivano scritti in anticipo per garantire che ciò che si codifica rimanesse corretto, date le specifiche che erano ora ben definite nel codice di test. Appare anche come un aspetto molto importante del refactoring, in quanto quando hai modificato il tuo codice, puoi fare affidamento sui test per dimostrare che non avevi modificato il comportamento del codice.

Poi gli strumenti sono arrivati e pensavano di conoscere le informazioni sul tuo codice e potevano quindi generare stub per aiutarti a scrivere i tuoi test unitari, e penso che questo sia stato il punto in cui tutto è andato storto.

Gli stub di test sono generati da un computer che non ha idea di che cosa si stia facendo, crea semplicemente uno stub per ogni metodo perché è ciò che viene detto di fare. Ciò significa che hai un test per ogni metodo, indipendentemente dalla complessità di tale metodo o se è adatto per il test in isolamento.

Questo sta arrivando a test dalla parte sbagliata della metodologia TDD. In TDD dovresti capire che cosa deve fare il codice, e quindi produrre codice che lo consenta. Questo è auto-avverante in quanto si finisce per scrivere test che provano che il codice fa ciò che fa il codice, non ciò che dovrebbe fare. Combinato con l'auto-generazione di stub di test basati sui metodi, ti perdi molto tempo a dimostrare ogni piccolo aspetto del tuo codice che può facilmente rivelarsi sbagliato quando tutti i piccoli pezzi sono messi insieme.

Quando Fowler descrisse i test nel suo libro, si riferì a testare ogni classe con il suo metodo principale. Ha migliorato questo concetto, ma il concetto è sempre lo stesso: test l'intera classe in modo che funzioni nel suo complesso, tutti i test sono raggruppati insieme per dimostrare l'interazione di tutti quei metodi in modo che la classe possa essere riutilizzata con aspettative definite.

Penso che i toolkit di test ci abbiano reso un disservizio, ci hanno portato a pensare che il toolkit sia l'unico modo per fare le cose quando veramente, devi pensare di più per te stesso per ottenere il miglior risultato dal tuo codice. Inserire in modo cieco il codice di test negli stub di prova per pezzi piccoli significa solo che devi ripetere il tuo lavoro in un test di integrazione in ogni caso (e se hai intenzione di farlo, perché non saltare completamente la fase di test dell'unità ridondante). Significa anche che le persone sprecano molto tempo cercando di ottenere una copertura del test del 100% e molto tempo a creare grandi quantità di codice di simulazione e dati che sarebbero stati meglio spesi rendendo il codice più facile al test di integrazione (cioè se si ha tanto dipendenze dei dati, il test dell'unità potrebbe non essere l'opzione migliore)

Infine, la fragilità dei test unitari basati sui metodi mostra solo il problema. Il refactoring è progettato per essere usato con i test unitari, se i tuoi test si rompono continuamente perché stai rifattorizzando qualcosa allora si è seriamente sbagliato con l'intero approccio. Il refactoring ama creare e cancellare metodi, quindi ovviamente l'approccio basato sul metodo cieco per metodo non è ciò che era originariamente inteso.

Non ho dubbi sul fatto che molti metodi faranno dei test scritti per loro, tutti i metodi pubblici di una classe dovrebbero essere testati, ma non puoi sfuggire al concetto di testarli insieme come parte di un singolo test case. Ad esempio, se ho un metodo set e get, posso scrivere test che inseriscono i dati e controllare che i membri interni siano impostati ok, oppure posso usare ciascuno per inserire alcuni dati e poi estrarlo di nuovo per vedere se è sempre uguale e non confuso. Questo sta testando la classe, non ogni metodo in isolamento. Se il setter si affida a un metodo privato helper, allora va bene - non è necessario prendere in giro il metodo privato per assicurarsi che il setter funzioni, non se si prova l'intera classe.

Penso che la religione stia entrando in questo argomento, quindi si vede lo scisma in quello che ora è conosciuto come lo sviluppo "guidato dal comportamento" e "test-driven" - il concetto originale di test unitario era per lo sviluppo guidato dal comportamento.

    
risposta data 20.08.2011 - 14:35
fonte
10

Un'unità è comunemente definita come " la più piccola parte testabile di un'applicazione ". Più spesso, sì, questo significa un metodo. E sì, questo significa che non dovresti testare il risultato di un metodo dipendente, ma solo che il metodo è chiamato (e poi solo una volta, se possibile, non in ogni test per quel metodo).

Lo chiami fragile. Penso che sia sbagliato. I test fragili sono quelli che si rompono con il minimo cambiamento in codice non correlato. Cioè, quelli che si basano su un codice che è irrilevante per la funzionalità in fase di test.

Tuttavia, quello che penso tu voglia dire veramente è che testare un metodo senza le sue dipendenze non è approfondito. Su questo punto sarei d'accordo. È inoltre necessario test di integrazione per assicurarsi che le unità di codice siano collegate correttamente per creare un'applicazione.

Questo è esattamente il problema che sviluppo basato sul comportamento e in particolare sviluppo basato su test di accettazione , si propone di risolvere. Ma ciò non elimina la necessità di test unitari / sviluppo guidato dai test; semplicemente lo completa.

    
risposta data 14.08.2011 - 22:42
fonte
2

Come suggerisce il nome, stai testando un soggetto atomico in ogni test. Un tal soggetto è solitamente un singolo metodo. Test multipli possono testare lo stesso metodo, al fine di coprire il percorso felice, possibili errori, ecc. Stai testando il comportamento, non la meccanica interna. Quindi il test unitario riguarda davvero il test di un'interfaccia pubblica di classe, vale a dire un metodo specifico.

Nei test unitari, un metodo deve essere testato separatamente, cioè stub / beffando / fingendo qualsiasi dipendenza. Altrimenti, testare un'unità con dipendenze "reali" lo rende un test di integrazione. C'è un tempo e un luogo per entrambi i tipi di test. Le unit test assicurano che un singolo soggetto funzioni come previsto, standalone. I test di integrazione assicurano che i soggetti "reali" lavorino insieme correttamente.

    
risposta data 14.08.2011 - 22:54
fonte
1

La mia regola empirica: la più piccola unità di codice che è ancora abbastanza complessa da contenere bug.

Se questo è un metodo o una classe o sottosistema dipende dal particolare codice, non può essere data alcuna regola generale.

Ad esempio, non fornisce alcun valore per testare semplici metodi getter / setter in isolamento, o metodi wrapper che chiamano solo un altro metodo. Anche un'intera classe potrebbe essere troppo semplice da testare, se la classe è solo un involucro o un adattatore sottile. Se l'unica cosa da verificare è se viene chiamato un metodo su una simulazione, allora il codice sotto test è sottodimensionato.

In altri casi, un singolo metodo potrebbe eseguire un calcolo complesso che è prezioso da testare separatamente.

In molti casi le parti complesse non sono classi individuali ma piuttosto l'integrazione tra classi. Quindi testerai due o più classi contemporaneamente. Alcuni diranno che non si tratta di test unitari ma di test di integrazione, ma non importa la terminologia: dovresti testare dove si trova la complessità, e questi test dovrebbero far parte della suite di test.

    
risposta data 02.06.2015 - 16:57
fonte

Leggi altre domande sui tag