Tipi di test unitari basati sull'utilità

13

Dal punto di vista del valore vedo due gruppi di test unitari nella mia pratica:

  1. Test che testano alcune logiche non banali. Scrivendoli (prima o dopo implementazione o dopo) rivela alcuni problemi / potenziali bug e aiuta ad essere fiducioso nel caso in cui la logica sia cambiata in futuro.
  2. Test che testano alcune logiche molto banali. Quei test più simili codice del documento (tipicamente con mock) di testarlo. La manutenzione il flusso di lavoro di quei test non è "qualche logica cambiata, il test è diventato rosso - grazie a Dio ho scritto questo test" ma "qualche codice banale è cambiato, il test è diventato falso negativo - devo mantenere (riscrivere) il test senza guadagnare alcun profitto ". Il più delle volte quelle prove non lo sono vale la pena mantenere (tranne motivi religiosi). E secondo il mio esperienza in molti sistemi quei test sono come l'80% di tutti i test.

Sto cercando di scoprire cosa pensano gli altri ragazzi sul tema della separazione dei test unitari in base al valore e in che modo corrisponde alla mia separazione. Ma quello che vedo per lo più è propaganda a tempo pieno di TDD o test-sono-inutili-solo-scrivere-la-propaganda del codice. Sono interessato a qualcosa nel mezzo. I tuoi pensieri o riferimenti a articoli / documenti / libri sono i benvenuti.

    
posta SiberianGuy 07.04.2014 - 20:15
fonte

5 risposte

13

Penso che sia naturale incontrare una divisione all'interno dei test unitari. Ci sono molte opinioni diverse su come farlo correttamente e naturalmente tutte le altre opinioni sono intrinsecamente sbagliate . Ci sono alcuni articoli su DrDobbs recentemente che esplorano questo stesso problema a cui mi collego alla fine della mia risposta.

Il primo problema che vedo con i test è che è facile sbagliare. Nella mia classe C ++ del college siamo stati esposti a test unitari sia nel primo che nel secondo semestre. Non sapevamo nulla della programmazione in generale in nessuno dei semestri: stavamo cercando di apprendere i fondamenti della programmazione tramite C ++. Ora immagina di dire agli studenti: "Oh, hey, hai scritto un piccolo calcolatore annuale delle imposte! Ora scrivi alcuni test unitari per assicurarti che funzioni correttamente." I risultati dovrebbero essere ovvi: erano tutti orribili, compresi i miei tentativi.

Una volta ammesso che fai schifo a scrivere test di unità e desideri migliorare, presto ti troverai di fronte a stili di test alla moda o metodologie differenti. Testando le metodologie mi riferisco a pratiche come test-first o cosa fa Andrew Binstock di DrDobbs, che è scrivere i test a fianco del codice. Entrambi hanno i loro pro e contro e mi rifiuto di entrare in ogni dettaglio soggettivo perché questo inciterà a una guerra di fuoco. Se non sei confuso su quale sia la metodologia di programmazione migliore, allora forse lo stile di test farà il trucco. Dovresti usare TDD, BDD, test basati su proprietà? JUnit ha concetti avanzati chiamati Teorie che offusca la linea tra TDD e test basati su Proprietà. Quale usare quando?

tl; dr È facile testare in modo errato, è incredibilmente supponente e non credo che una qualsiasi metodologia di test sia intrinsecamente migliore fintanto che sono usate diligentemente e professionalmente nel contesto che sono anche appropriati. Inoltre, il test è nella mia mente un'estensione delle asserzioni o dei test di sanità mentale che garantivano un approccio ad hoc fail-fast allo sviluppo che ora è molto, molto più facile.

Per un'opinione soggettiva, preferisco scrivere "fasi" di test, per mancanza di una frase migliore. Scrivo test unitari che testano le classi in isolamento, usando i mock dove necessario. Questi saranno probabilmente eseguiti con JUnit o qualcosa di simile. Poi scrivo test di integrazione o di accettazione, questi vengono eseguiti separatamente e di solito solo poche volte al giorno. Questi sono il tuo caso d'uso non banale. Di solito uso BDD perché è bello esprimere caratteristiche in linguaggio naturale, qualcosa che JUnit non può facilmente fornire.

Infine, risorse. Questi presenteranno opinioni contrastanti incentrate principalmente su test unitari in diverse lingue e con diversi quadri. Dovrebbero presentare il divario nell'ideologia e nella metodologia, permettendoti di formulare una tua opinione purché non abbia già manipolato troppo il tuo:)

[1] The Corruption of Agile di Andrew Binstock

[2] Risposta alle risposte dell'articolo precedente

[3] Risposta alla corruzione di Agile da parte di Zio Bob

[4] Risposta alla corruzione di Agile di Rob Myers

[5] Perché preoccuparsi dei test sui cetrioli?

[6] Stai cuocendo male

[7] Allontanati dagli strumenti

[8] Commentario su 'Numeri romani Kata con commento'

[9] Numeri romani Kata con commento

    
risposta data 07.04.2014 - 20:44
fonte
5

Credo sia importante avere test di entrambi i tipi e usarli laddove appropriato.

Come hai detto tu, ci sono due estremi e io sinceramente non sono d'accordo nemmeno con uno dei due.

La chiave è che i test unitari devono coprire le regole e i requisiti aziendali . Se è necessario che il sistema tenga traccia dell'età di una persona, scrivere test "banali" per assicurarsi che l'età sia un numero intero non negativo. Stai testando il dominio dei dati richiesti dal sistema: mentre è banale, ha valore perché impone i parametri del sistema .

Allo stesso modo con test più complessi, devono apportare valore. Certo, puoi scrivere un test che convalida qualcosa che non è un requisito ma che dovrebbe essere applicato in una torre d'avorio da qualche parte, ma è meglio spendere tempo a scrivere test che convalidano i requisiti per i quali il cliente ti paga. Ad esempio, perché scrivere un test che convalida il tuo codice può gestire un flusso di input che va in timeout, quando gli unici flussi provengono da file locali, non dalla rete?

Credo fermamente nei test unitari e utilizzo TDD ovunque abbia senso. I test unitari apportano certamente valore sotto forma di maggiore qualità e comportamento "fail-fast" quando si cambia codice. Tuttavia, c'è una vecchia regola 80/20 da tenere a mente. Ad un certo punto si raggiungono rendimenti decrescenti quando si scrivono i test e si ha bisogno di passare a un lavoro più produttivo anche se c'è un certo valore misurabile da scrivere più test.

    
risposta data 09.04.2014 - 21:57
fonte
4

Ecco il mio punto di vista: tutti i test hanno dei costi:

  • tempo iniziale e impegno:
    • pensa a cosa testare e come testarlo
    • implementa il test e assicurati che stia testando cosa dovrebbe
  • manutenzione continua
    • assicurandoti che il test stia ancora facendo quello che dovrebbe fare mentre il codice si evolve naturalmente
  • eseguendo il test
    • tempo di esecuzione
    • analizzando i risultati

Intendiamo anche che tutti i test forniscano vantaggi (e nella mia esperienza, quasi tutti i test forniscono benefici):

  • specifiche
  • evidenzia casi d'angolo
  • previene la regressione
  • verifica automatica
  • esempi dell'uso dell'API
  • quantificazione di proprietà specifiche (tempo, spazio)

Quindi è abbastanza facile vedere che se scrivi una serie di test, probabilmente avranno un certo valore. Dove questo si complica è quando inizi a confrontare quel valore (che, a proposito, potresti non sapere in anticipo - se passi il tuo codice, i test di regressione perdono il loro valore) al costo.

Ora, il tuo tempo e i tuoi sforzi sono limitati. Ti piacerebbe scegliere di fare quelle cose che offrono il massimo beneficio al minor costo. E penso che sia una cosa molto difficile da fare, non ultimo perché potrebbe richiedere la conoscenza che uno non ha o sarebbe costoso da ottenere.

E questa è la vera frattura tra questi diversi approcci. Credo che tutti abbiano identificato strategie di test che sono vantaggiose. Tuttavia, ogni strategia ha costi e benefici diversi in generale. Inoltre, i costi e i benefici di ciascuna strategia dipenderanno molto dalle specifiche del progetto, del dominio e del team. In altre parole, potrebbero esserci più risposte migliori.

In alcuni casi, il pompaggio del codice senza test può fornire i migliori vantaggi / costi. In altri casi, una completa suite di test potrebbe essere migliore. In altri casi ancora, migliorare il design potrebbe essere la cosa migliore da fare.

    
risposta data 09.04.2014 - 21:06
fonte
2

Che test un unità , davvero? E c'è davvero una grossa dicotomia in gioco qui?

Lavoriamo in un campo in cui la lettura letteralmente un po 'oltre la fine di un buffer può bloccare completamente un programma o causare un risultato totalmente impreciso, o come evidenziato dal recente bug TLS "HeartBleed" sistema sicuro spalancato senza produrre alcuna prova diretta del difetto.

È impossibile eliminare tutta la complessità da questi sistemi. Ma il nostro compito è, per quanto possibile, minimizzare e gestire tale complessità.

Un'unità prova un test che conferma, ad esempio, che una prenotazione viene registrata con successo in tre sistemi diversi, che viene creata una voce di registro e viene inviata una conferma via email?

Ho intenzione di dire no . Questo è un test di integrazione . E quelli sicuramente hanno il loro posto, ma sono anche un argomento diverso.

Un test di integrazione funziona per confermare la funzione complessiva di un'intera "funzione". Ma il codice che sta dietro questa caratteristica dovrebbe essere scomposto in semplici blocchi testabili, chiamati anche "unità".

Quindi un test unitario dovrebbe avere un ambito molto limitato.

Che implica che il codice testato da il test dell'unità dovrebbe avere un ambito molto limitato.

Il che implica inoltre che uno dei pilastri del buon design è quello di rompere il tuo complesso problema in pezzi più piccoli, per un singolo scopo (nella misura del possibile) che possono essere testati in un relativo isolamento l'uno dall'altro.

Ciò che si ottiene è un sistema costituito da componenti di base affidabili, e si sa se una qualsiasi di queste unità fondamentali di codice si interrompe perché hai scritto test semplici, di portata limitata per dirti esattamente quello.

In molti casi dovresti anche probabilmente avere più test per unità. Gli stessi test dovrebbero essere semplici, testando un solo e unico comportamento nella misura del possibile.

La nozione di un "test unitario" per testare una logica complessa non complessa, è, credo, un po 'un ossimoro.

Quindi, se questo tipo di rottura deliberata del progetto ha avuto luogo, allora come nel mondo un test unitario potrebbe improvvisamente iniziare a produrre falsi positivi, a meno che la funzione di base dell'unità di codice testata sia cambiata ? E se che è successo, allora è meglio che ci siano alcuni effetti a catena non ovvi in gioco. Il tuo test non funzionante, quello che sembra produrre un falso positivo, ti avverte in realtà che alcuni cambiamenti hanno infranto una più ampia cerchia di dipendenze nel codice base, e devono essere esaminati e risolti.

Alcune di queste unità (molte di queste) potrebbero aver bisogno di essere testate usando oggetti finti, ma ciò non significa che devi scrivere test più complessi o elaborati.

Tornando al mio esempio forzato di un sistema di prenotazione, non puoi davvero inviare richieste a un database di prenotazione in diretta oa un servizio di terze parti (o persino a un'istanza di "dev") ogni volta che unità verifica il tuo codice.

Quindi usi mock che presentano lo stesso contratto di interfaccia. I test possono quindi validare il comportamento di un pezzo di codice relativamente piccolo e deterministico. Verde in fondo al tabellone poi ti dice che i blocchi che costituiscono la tua fondazione non sono infranti.

Ma la logica dei singoli test di unità rimane la più semplice possibile.

    
risposta data 13.04.2014 - 02:03
fonte
1

Questo è, naturalmente, solo il mio punto di vista, ma aver trascorso gli ultimi mesi ad apprendere la programmazione funzionale in fsharp (proveniente da uno sfondo in C #) mi ha fatto realizzare alcune cose.

Come indicato dall'OP, in genere ci sono 2 tipi di "test unitari" che vediamo ogni giorno. Test che coprono gli input e gli out di un metodo, che sono generalmente i più validi, ma sono difficili da fare per l'80% del sistema, che è meno relativo agli "algoritmi" e più alle "astrazioni".

L'altro tipo, sta testando l'interattività dell'astrazione, in genere coinvolge il mocking. A mio parere, questi test sono per lo più necessari a causa della progettazione della vostra applicazione. Ommettendoli, rischi di avere strani bug e codice spagetti, perché le persone non pensano al loro design correttamente, a meno che non siano costretti a fare i test prima (e anche allora, di solito rovinano tutto). Il problema non è tanto la metodologia di test, quanto il design sottostante del sistema. La maggior parte dei sistemi costruiti con linguaggio imperativo o OO hanno una dipendenza intrinseca da "effetti collaterali", ovvero "Fai questo, ma non dirmi nulla". Quando fai affidamento sull'effetto collaterale, devi testarlo, perché in genere un requisito o un'operazione commerciale ne fa parte.

Quando si progetta il sistema in un modo più funzionale, evitando di creare dipendenze dagli effetti collaterali ed evitare modifiche dello stato / tracciamento attraverso l'immutabilità, è possibile concentrarsi maggiormente sui test "fuori e dentro", che testano chiaramente più l'azione, e meno come ci si arriva. Rimarrai stupito dal fatto che cose come l'immutabilità possano darti in termini di soluzioni molto più semplici agli stessi problemi, e quando non sei più dipendente da "effetti collaterali" puoi fare cose come la parallelizzazione e la programmazione asincrona con quasi nessun costo aggiuntivo.

Da quando ho iniziato a scrivere codice in Fsharp, non ho mai avuto bisogno di un sistema di derisione per nulla e ho addirittura perso la mia dipendenza in un container IOC. I miei test sono guidati da esigenze e valore aziendali e non da pesanti livelli di astrazione tipicamente necessari per raggiungere la composizione in una programmazione imperativa.

    
risposta data 13.04.2014 - 03:45
fonte

Leggi altre domande sui tag