È possibile aggiungere correttamente i test unitari in un progetto di produzione esistente? In tal caso, come e ne vale la pena?


140

Sto fortemente valutando di aggiungere test unitari a un progetto esistente in produzione. È stato avviato 18 mesi fa prima che potessi davvero vedere qualsiasi beneficio di TDD (face palm) , quindi ora è una soluzione piuttosto grande con una serie di progetti e non ho la più pallida idea da dove iniziare con l'aggiunta di unit test. Ciò che mi sta facendo considerare questo è che a volte un vecchio bug sembra riemergere, o un bug è registrato come riparato senza essere realmente risolto. Il test unitario ridurrebbe o eviterebbe questi problemi.

Leggendo domande simili su SO, ho visto raccomandazioni come iniziare dal tracker dei bug e scrivere un test case per ogni bug per prevenire la regressione. Tuttavia, sono preoccupato che finirò per perdere il quadro generale e finirò per perdere i test fondamentali che sarebbero stati inclusi se avessi usato TDD sin dall'inizio.

Esistono processi / passaggi che devono essere rispettati per garantire che una soluzione esistente sia adeguatamente testata dall'unità e non solo integrata? Come posso assicurarmi che i test siano di buona qualità e che non siano un caso qualsiasi test è meglio di nessun test .

Quindi immagino che anche quello che sto chiedendo sia;

  • Vale la pena lo sforzo per una soluzione esistente in produzione?
  • Meglio ignorare i test per questo progetto e aggiungerlo in una possibile riscrittura futura?
  • Cosa sarà più vantaggioso; trascorrere qualche settimana aggiungendo test o qualche settimana aggiungendo funzionalità?

(Ovviamente la risposta al terzo punto dipende interamente dal fatto che tu stia parlando con il management o uno sviluppatore)


Motivo per Bounty

Aggiungendo una generosità per cercare di attirare una gamma più ampia di risposte che non solo confermano il mio attuale sospetto che sia una buona cosa da fare, ma anche alcune buone ragioni contro.

Ho intenzione di scrivere questa domanda in seguito con pro e contro per provare a mostrare al management che vale la pena dedicare ore all'uomo per spostare il futuro sviluppo del prodotto su TDD. Voglio affrontare questa sfida e sviluppare il mio ragionamento senza il mio punto di vista parziale.


11
Link obbligatorio al libro di Michael Feathers sull'argomento: amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/…
Mark Rushakoff

1
@Mark - Grazie, è nel link che ho fornito. Speravo di ottenere una risposta decente senza comprare un altro libro ... anche se non puoi mai avere troppi libri (a meno che tu non voglia fare un lavoro fatto).
djdd87,

1
Hai davvero bisogno di leggere questo libro :) È uno dei miei preferiti e aiuta davvero a capire la tensione tra refactoring e test automatizzati.
manuel aldana,

Risposte:


177

Ho introdotto test unitari su basi di codice che non lo avevano in precedenza. L'ultimo grande progetto in cui sono stato coinvolto è stato quello in cui il prodotto era già in produzione con zero unit test quando sono arrivato al team. Quando me ne sono andato - 2 anni dopo - abbiamo fatto oltre 4500 test che hanno prodotto circa il 33% di copertura del codice in una base di codice con 230.000 + LOC di produzione (applicazione Win-Form finanziaria in tempo reale). Ciò può sembrare basso, ma il risultato è stato un significativo miglioramento della qualità del codice e del tasso di difetto, oltre a un miglioramento del morale e della redditività.

Può essere fatto quando si ha una comprensione e un impegno accurati delle parti coinvolte.

Prima di tutto, è importante capire che il test unitario è un'abilità in sé. Puoi essere un programmatore molto produttivo secondo gli standard "convenzionali" e avere ancora difficoltà a scrivere unit test in un modo che si ridimensiona in un progetto più ampio.

Inoltre, e specificamente per la tua situazione, l'aggiunta di unit test a una base di codice esistente che non ha test è anche un'abilità specializzata in sé. A meno che tu o qualcuno del tuo team non abbia un'esperienza di successo nell'introduzione di unit test in una base di codice esistente, direi che leggere il libro di Feather è un requisito (non facoltativo o fortemente raccomandato).

Effettuare il passaggio al test unitario del codice è un investimento in persone e competenze tanto quanto nella qualità della base di codice. Capire questo è molto importante in termini di mentalità e gestione delle aspettative.

Ora, per i tuoi commenti e domande:

Tuttavia, sono preoccupato che finirò per perdere il quadro generale e finirò per perdere i test fondamentali che sarebbero stati inclusi se avessi usato TDD sin dall'inizio.

Risposta breve: Sì, ti mancheranno i test e sì, inizialmente potrebbero non assomigliare a quello che avrebbero in una situazione di campo verde.

La risposta più approfondita è questa: non importa. Inizi senza test. Inizia ad aggiungere test e refactor mentre procedi. Man mano che i livelli di abilità migliorano, inizia ad alzare l'asticella per tutto il codice appena scritto aggiunto al tuo progetto. Continua a migliorare ecc ...

Ora, leggendo tra le righe qui ho l'impressione che questo provenga dalla mentalità della "perfezione come scusa per non agire". Una mentalità migliore è concentrarsi sulla fiducia in se stessi. Quindi, poiché potresti non sapere ancora come farlo, capirai come andare e riempire gli spazi vuoti. Pertanto, non c'è motivo di preoccuparsi.

Ancora una volta, è un'abilità. Non è possibile passare da zero test alla perfezione TDD in un "processo" o in un approccio "passo dopo passo" in modo lineare. Sarà un processo. Le vostre aspettative devono essere di fare progressi e miglioramenti graduali e incrementali. Non esiste una pillola magica.

La buona notizia è che con il passare dei mesi (e persino degli anni), il tuo codice inizierà gradualmente a diventare un codice "corretto" ben ponderato e ben testato.

Come nota a margine. Scoprirai che l'ostacolo principale all'introduzione di unit test in una vecchia base di codice è la mancanza di coesione e dipendenze eccessive. Probabilmente scoprirai quindi che l'abilità più importante diventerà come rompere le dipendenze esistenti e disaccoppiare il codice, piuttosto che scrivere i test unitari effettivi stessi.

Esistono processi / passaggi che devono essere seguiti per garantire che una soluzione esistente sia adeguatamente testata dall'unità e non solo integrata?

A meno che non lo possieda già, imposta un server di build e una build di integrazione continua che viene eseguita su ogni check-in, inclusi tutti i test unitari con copertura del codice.

Allena la tua gente.

Inizia da qualche parte e inizia ad aggiungere test mentre fai progressi dal punto di vista del cliente (vedi sotto).

Utilizzare la copertura del codice come riferimento guida della quantità di codice di produzione in fase di test.

Il tempo di costruzione dovrebbe essere sempre VELOCE. Se il tuo tempo di costruzione è lento, le tue abilità di testing dell'unità sono in ritardo. Trova i test lenti e migliorali (disaccoppia il codice di produzione e prova in isolamento). Ben scritto, dovresti essere facilmente in grado di avere diverse migliaia di test unitari e comunque completare un build in meno di 10 minuti (~ 1-ms-test / è una linea guida buona ma molto approssimativa, alcune eccezioni possono essere applicate come il codice usando la riflessione ecc. ).

Ispeziona e adatta.

Come posso garantire che i test siano di buona qualità e che non siano solo un caso di test è meglio di nessun test.

Il tuo giudizio deve essere la tua fonte primaria di realtà. Non esiste una metrica in grado di sostituire l'abilità.

Se non si dispone di tale esperienza o giudizio, prendere in considerazione la possibilità di contrarre qualcuno che lo fa.

Due indicatori secondari approssimativi sono la copertura totale del codice e la velocità di costruzione.

Vale la pena lo sforzo per una soluzione esistente in produzione?

Sì. La maggior parte del denaro speso per un sistema o una soluzione personalizzati viene speso dopo essere stato messo in produzione. E investire in qualità, persone e competenze non dovrebbe mai essere fuori moda.

Meglio ignorare i test per questo progetto e aggiungerlo in una possibile riscrittura futura?

Dovresti prendere in considerazione non solo l'investimento in persone e competenze, ma soprattutto il costo totale di proprietà e la durata prevista del sistema.

La mia risposta personale sarebbe "sì, certo" nella maggior parte dei casi perché so che è molto meglio, ma riconosco che potrebbero esserci delle eccezioni.

Cosa sarà più vantaggioso; trascorrere qualche settimana aggiungendo test o qualche settimana aggiungendo funzionalità?

Né. Il tuo approccio dovrebbe essere quello di aggiungere test alla tua base di codice MENTRE stai facendo progressi in termini di funzionalità.

Ancora una volta, si tratta di un investimento in persone, competenze E qualità della base di codice e come tale richiederà tempo. I membri del team devono imparare come rompere le dipendenze, scrivere test unitari, imparare nuovi habbit, migliorare la disciplina e la consapevolezza della qualità, come progettare meglio il software, ecc. È importante capire che quando inizi ad aggiungere test i tuoi membri del team probabilmente non lo fanno possedere queste capacità al livello necessario per l'approccio per avere successo, quindi fermare i progressi per passare tutto il tempo ad aggiungere molti test semplicemente non funzionerà.

Inoltre, aggiungere test unitari a una base di codice esistente di qualsiasi dimensione di progetto considerevole è un'impresa GRANDE che richiede impegno e persistenza. Non puoi cambiare qualcosa di fondamentale, aspettarti molto apprendimento lungo la strada e chiedere al tuo sponsor di non aspettarsi alcun ROI arrestando il flusso di valore aziendale. Quello non volerà, e francamente non dovrebbe.

In terzo luogo, si desidera instillare validi valori di concentrazione aziendale nel proprio team. La qualità non viene mai a spese del cliente e non puoi andare veloce senza qualità. Inoltre, il cliente vive in un mondo che cambia e il tuo compito è semplificare l'adattamento. L'allineamento del cliente richiede sia la qualità che il flusso del valore aziendale.

Quello che stai facendo è pagare il debito tecnico. E lo stai facendo, pur servendo i tuoi clienti in continua evoluzione delle esigenze. Man mano che il debito viene ripagato, la situazione migliora ed è più facile servire meglio il cliente e offrire più valore. Ecc. Questo slancio positivo è ciò a cui dovresti puntare perché sottolinea i principi di un ritmo sostenibile e manterrà e migliorerà il morale - sia per il tuo team di sviluppo, il tuo cliente e le tue parti interessate.

spero che aiuti


Riflette la mia stessa mentalità. Attualmente sto mostrando questo esatto processo / metodo per introdurre casi di test in un'applicazione JAVA di grandi dimensioni. :)
Darshan Joshi il

3
Questa è la migliore risposta articolata su qualsiasi argomento che abbia mai letto su StackOverflow. Molto bene! Se non lo hai fatto, ti esorto a prendere in considerazione la possibilità di scrivere un libro sulla tua risposta all'ultima domanda.
Yermo Lamers

Grazie Yermo. Non sono sicuro di avere il tempo di scrivere un libro. Ma forse potrei scrivere un articolo o due sul blog. Ho appena iniziato il mio nuovo blog, quindi potrebbe essere un po 'di tempo.
Mahol25,

2
Questo consiglio ai test di unità è un consiglio generale sulla vita. Sul serio.
Wjdavis5,

24
  • Vale la pena lo sforzo per una soluzione esistente in produzione?

Sì!

  • Meglio ignorare i test per questo progetto e aggiungerlo in una possibile riscrittura futura?

No!

  • Cosa sarà più vantaggioso; trascorrere qualche settimana aggiungendo test o qualche settimana aggiungendo funzionalità?

L'aggiunta di test (in particolare test automatizzati) semplifica notevolmente il funzionamento del progetto in futuro e rende significativamente meno probabile la consegna all'utente di stupidi problemi.

I test da mettere a priori sono quelli che controllano se ciò che ritieni sia l'interfaccia pubblica al tuo codice (e ogni modulo in esso) funziona nel modo in cui pensi. Se puoi, prova a indurre anche ogni modalità di errore isolata che i tuoi moduli di codice dovrebbero avere (nota che questo può essere non banale e dovresti stare attento a non controllare troppo attentamente come le cose falliscono, ad esempio, non vuoi davvero per fare cose come contare il numero di messaggi di log prodotti in caso di errore, poiché è sufficiente verificare che sia registrato).

Quindi inserire un test per ciascun bug corrente nel database dei bug che induca esattamente il bug e che passerà quando il bug viene risolto. Quindi correggi quei bug! :-)

L'aggiunta dei test richiede tempo in anticipo, ma vieni rimborsato più volte nel back-end poiché il tuo codice risulta di qualità molto superiore. Ciò conta enormemente quando si tenta di spedire una nuova versione o eseguire interventi di manutenzione.


Grazie per la risposta elaborata. Confermato quello che stavo provando. Vedrà quali altre risposte vengono pubblicate prima di esprimere il mio voto e accettare comunque.
djdd87,

15

Il problema con i test unitari di retrofitting è che ti rendi conto che non hai pensato di iniettare una dipendenza qui o di utilizzare un'interfaccia lì, e in poco tempo riscriverai l'intero componente. Se hai tempo per farlo, ti costruirai una bella rete di sicurezza, ma potresti aver introdotto bug sottili lungo la strada.

Sono stato coinvolto in molti progetti che avevano davvero bisogno di unit test fin dal primo giorno, e non esiste un modo semplice per inserirli lì, a meno di una riscrittura completa, che di solito non può essere giustificata quando il codice funziona e fa già soldi. Di recente, ho fatto ricorso alla scrittura di script PowerShell che esercitano il codice in modo da riprodurre un difetto non appena viene sollevato e quindi mantengono questi script come una suite di test di regressione per ulteriori modifiche lungo la linea. In questo modo puoi almeno iniziare a creare alcuni test per l'applicazione senza cambiarlo troppo, tuttavia, questi sono più simili ai test di regressione end-to-end che ai test unitari corretti.


Se il codice è suddiviso abbastanza bene dall'inizio, è abbastanza facile testarlo. Il problema si presenta quando hai un codice che è tutto ricucito e dove gli unici punti di prova esposti sono i test di integrazione completa. (Ho un codice del genere in cui la testabilità è quasi zero a causa della quantità che fa affidamento su altri componenti che non sono facili da deridere e in cui la distribuzione richiede un riavvio del server. Testabile ... ma difficile da fare bene.)
Donal Fellows

13

Sono d'accordo con quello che la maggior parte degli altri ha detto. L'aggiunta di test al codice esistente è utile. Non sarò mai in disaccordo con questo punto, ma vorrei aggiungere un avvertimento.

Sebbene l'aggiunta di test al codice esistente sia preziosa, ha un costo. Viene a costo di non sviluppare nuove funzionalità. Il modo in cui queste due cose si bilanciano dipende interamente dal progetto e ci sono un certo numero di variabili.

  • Quanto ci vorrà per mettere alla prova tutto quel codice? Giorni? Settimane? Mesi? Anni?
  • Per chi stai scrivendo questo codice? Pagare i clienti? Un professore? Un progetto open source?
  • Com'è il tuo programma? Hai delle scadenze difficili da rispettare? Hai delle scadenze?

Ancora una volta, mi permetta di sottolineare, i test sono preziosi e dovresti lavorare per mettere alla prova il tuo vecchio codice. Questo è davvero più una questione di come lo approcci. Se puoi permetterti di eliminare tutto e mettere alla prova tutto il tuo vecchio codice, fallo. Se ciò non è realistico, ecco cosa dovresti fare almeno

  • Qualsiasi nuovo codice che scrivi dovrebbe essere completamente sotto test unitario
  • Qualsiasi vecchio codice che tocchi (correzione di bug, estensione, ecc.) Dovrebbe essere messo sotto test unitario

Inoltre, questa non è una proposta tutto o niente. Se hai una squadra di, diciamo, quattro persone, e puoi rispettare le tue scadenze mettendo una o due persone in servizio di test legacy, lo fai sicuramente.

Modificare:

Ho intenzione di scrivere questa domanda in seguito con pro e contro per provare a mostrare al management che vale la pena dedicare ore all'uomo per spostare il futuro sviluppo del prodotto su TDD.

È come chiedere "Quali sono i pro e i contro nell'uso del controllo del codice sorgente?" o "Quali sono i pro ei contro per intervistare le persone prima di assumerle?" o "Quali sono i pro e i contro della respirazione?"

A volte c'è solo un lato dell'argomento. È necessario disporre di test automatici di qualche forma per qualsiasi progetto di qualsiasi complessità. No, i test non si scrivono da soli e, sì, ci vorrà un po 'di tempo in più per far uscire le cose. Ma alla lunga ci vorrà più tempo e costi più denaro per correggere i bug dopo il fatto che scrivere i test in anticipo. Periodo. Questo è tutto quello che c'è da fare.


9

Quando abbiamo iniziato ad aggiungere i test, è stato per un codebase di dieci anni, approssimativamente un milione di righe, con troppa logica nell'interfaccia utente e nel codice di reporting.

Una delle prime cose che abbiamo fatto (dopo aver impostato un server di build continuo) è stata l'aggiunta di test di regressione. Questi erano test end-to-end.

  • Ogni suite di test inizia inizializzando il database su uno stato noto. In realtà abbiamo dozzine di set di dati di regressione che conserviamo in Subversion (in un repository separato dal nostro codice, a causa delle dimensioni pure). FixtureSetUp di ogni test copia uno di questi set di dati di regressione in un database temporaneo, quindi viene eseguito da lì.
  • La configurazione del dispositivo di prova esegue quindi alcuni processi di cui siamo interessati ai risultati. (Questo passaggio è facoltativo: alcuni test di regressione esistono solo per testare i report.)
  • Quindi ogni test esegue un report, lo genera in un file .csv e confronta il contenuto di quel .csv con uno snapshot salvato. Questi .csv di snapshot sono archiviati in Subversion accanto a ciascun set di dati di regressione. Se l'output del report non corrisponde all'istantanea salvata, il test ha esito negativo.

Lo scopo dei test di regressione è di dirti se qualcosa cambia. Ciò significa che falliscono se hai rotto qualcosa, ma falliscono anche se hai cambiato qualcosa apposta (nel qual caso la correzione è aggiornare il file di snapshot). Non sai che i file dell'istantanea sono anche corretti - potrebbero esserci dei bug nel sistema (e quindi quando li correggi, i test di regressione falliranno).

Tuttavia, i test di regressione sono stati una grande vittoria per noi. Quasi tutto nel nostro sistema ha un rapporto, quindi trascorrendo alcune settimane a ottenere un cablaggio di prova attorno ai rapporti, siamo stati in grado di ottenere un certo livello di copertura su gran parte della nostra base di codice. Scrivere i test unitari equivalenti avrebbe richiesto mesi o anni. (I test unitari ci avrebbero fornito una copertura molto migliore e sarebbero stati molto meno fragili; ma preferirei avere qualcosa adesso, piuttosto che aspettare anni per la perfezione.)

Quindi siamo tornati indietro e abbiamo iniziato ad aggiungere test unitari quando abbiamo corretto i bug, o aggiunto miglioramenti, o necessari per comprendere un po 'di codice. I test di regressione non eliminano in alcun modo la necessità di test unitari; sono solo una rete di sicurezza di primo livello, in modo da ottenerne un po ' rapidamente livello di copertura dei test. Quindi puoi iniziare il refactoring per interrompere le dipendenze, quindi puoi aggiungere unit test; e i test di regressione ti danno la certezza che il tuo refactoring non sta rompendo nulla.

I test di regressione hanno problemi: sono lenti e ci sono troppi motivi per cui possono rompersi. Ma almeno per noi, erano così vale la pena. Negli ultimi cinque anni hanno catturato innumerevoli bug e li hanno catturati nel giro di poche ore, piuttosto che aspettare un ciclo di controllo qualità. Abbiamo ancora quei test di regressione originali, distribuiti su sette diverse macchine a costruzione continua (separate da quella che esegue i test di unità veloci) e di tanto in tanto li aggiungiamo di tanto in tanto perché abbiamo ancora così tanto codice che i nostri 6.000 + i test unitari non coprono.


8

Ne vale assolutamente la pena. La nostra app ha regole complesse di convalida incrociata e recentemente abbiamo dovuto apportare modifiche significative alle regole aziendali. Siamo finiti con conflitti che hanno impedito all'utente di salvare. Mi sono reso conto che ci sarebbe voluta un'eternità per risolverlo nell'applicazione (ci vogliono diversi minuti solo per arrivare al punto in cui si sono verificati i problemi). Avrei voluto introdurre test di unità automatizzati e avere installato il framework, ma non avevo fatto nulla oltre a un paio di test fittizi per assicurarmi che tutto funzionasse. Con le nuove regole commerciali in mano, ho iniziato a scrivere test. I test hanno identificato rapidamente le condizioni che hanno causato i conflitti e siamo riusciti a chiarire le regole.

Se scrivi test che coprono la funzionalità che stai aggiungendo o modificando, otterrai un vantaggio immediato. Se aspetti una riscrittura, potresti non avere mai test automatici.

Non dovresti perdere molto tempo a scrivere test per cose esistenti che già funzionano. Il più delle volte, non hai una specifica per il codice esistente, quindi la cosa principale che stai testando è la tua capacità di reverse engineering. D'altra parte, se hai intenzione di modificare qualcosa, devi coprire quella funzionalità con i test in modo da sapere che hai apportato le modifiche correttamente. E, naturalmente, per le nuove funzionalità, scrivi i test che falliscono, quindi implementa le funzionalità mancanti.


6

Aggiungerò la mia voce e dirò di sì, è sempre utile!

Ci sono alcune distinzioni che dovresti tenere a mente, però: scatola nera contro scatola bianca e unità vs funzionale. Poiché le definizioni variano, ecco cosa intendo per questi:

  • Black-box = test che sono stati scritti senza una conoscenza specifica dell'implementazione, in genere controllando i casi limite per assicurarsi che le cose accadano come un utente ingenuo si aspetterebbe.
  • White-box = test scritti con conoscenza dell'implementazione, che spesso cercano di esercitare punti di errore noti.
  • Test unitari = test di singole unità (funzioni, moduli separabili, ecc.). Ad esempio: assicurarsi che la classe dell'array funzioni come previsto e che la funzione di confronto delle stringhe restituisca i risultati previsti per un'ampia gamma di input.
  • Test funzionali = test dell'intero sistema in una sola volta. Questi test eserciteranno una grande parte del sistema tutto in una volta. Ad esempio: init, apri una connessione, fai alcune cose del mondo reale, chiudi, termina. Mi piace fare una distinzione tra questi e test unitari, perché hanno uno scopo diverso.

Quando ho aggiunto i test a un prodotto di spedizione nella fase avanzata del gioco, ho scoperto che ho ottenuto il massimo dal dollaro dalla scatola bianca e funzionale test . Se c'è una parte del codice che sai essere particolarmente fragile, scrivi dei test in white box per coprire i casi problematici per assicurarti che non si rompano allo stesso modo due volte. Allo stesso modo, i test funzionali dell'intero sistema sono un utile controllo di integrità che ti aiuta a non rompere mai i 10 casi d'uso più comuni.

Anche i black-box e i test unitari di piccole unità sono utili, ma se il tempo è limitato, è meglio aggiungerli in anticipo. Al momento della spedizione, in genere hai riscontrato (nel modo più duro) la maggior parte dei casi limite e i problemi che questi test avrebbero riscontrato.

Come gli altri, ti ricorderò anche le due cose più importanti sul TDD:

  1. La creazione di test è un lavoro continuo. Non si ferma mai. Dovresti provare ad aggiungere nuovi test ogni volta che scrivi un nuovo codice o modifichi un codice esistente.
  2. La tua suite di test non è mai infallibile! Non lasciare che il fatto che tu abbia dei test ti cali in un falso senso di sicurezza. Solo perché supera la suite di test non significa che funzioni correttamente o che non sia stata introdotta una sottile regressione delle prestazioni, ecc.

4

Se vale la pena aggiungere test unitari a un'app in produzione dipende dal costo di gestione dell'app. Se l'app ha pochi bug e richieste di miglioramento, forse non ne vale la pena. OTOH, se l'app è difettosa o modificata di frequente, i test unitari saranno estremamente utili.

A questo punto, ricorda che sto parlando dell'aggiunta selettiva di unit test, non di provare a generare una suite di test simili a quelli che esisterebbero se avessi praticato TDD dall'inizio. Pertanto, in risposta alla seconda metà della seconda domanda: fai un punto sull'uso di TDD sul tuo prossimo progetto, che si tratti di un nuovo progetto o di una riscrittura (scuse, ma ecco un link ad un altro libro che dovresti davvero leggere : Software orientato agli oggetti in crescita guidato da test )

La mia risposta alla tua terza domanda è la stessa della prima: dipende dal contesto del tuo progetto.

Incorporata all'interno della tua posta è un'ulteriore domanda su come garantire che tutti i test retro-adattati vengano eseguiti correttamente . La cosa importante da garantire è che i test unitari siano davvero test unitari , e questo (il più delle volte) significa che i test di retrofitting richiedono il refactoring del codice esistente per consentire il disaccoppiamento dei livelli / componenti (cfr. Iniezione di dipendenza; inversione del controllo; stub; beffardo). Se non si applica questo, i test diventano test di integrazione, che sono utili, ma meno mirati e più fragili dei veri test unitari.


4

Non menzioni il linguaggio di implementazione, ma se in Java puoi provare questo approccio:

  1. In un albero di origine separato costruisci test di regressione o "fumo", utilizzando uno strumento per generarli, che potrebbero avvicinarti all'80% di copertura. Questi test eseguono tutti i percorsi logici del codice e verificano da quel momento in poi che il codice fa esattamente quello che fa attualmente (anche se è presente un bug). Ciò fornisce una rete di sicurezza contro il cambiamento involontario del comportamento quando si esegue il refactoring necessario per rendere il codice facilmente testabile a mano.

  2. Per ogni bug che correggi, o funzionalità che aggiungi da ora in poi, usa un approccio TDD per assicurarti che il nuovo codice sia progettato per essere testabile e colloca questi test in un normale albero di sorgenti di test.

  3. Il codice esistente dovrà probabilmente essere modificato o refactored per renderlo testabile come parte dell'aggiunta di nuove funzionalità; i test del fumo ti forniranno una rete di sicurezza contro le regressioni o le modifiche involontarie al comportamento.

  4. Quando si apportano modifiche (correzioni di bug o funzionalità) tramite TDD, al termine è probabile che il test del fumo del compagno non vada a buon fine. Verificare che i guasti siano quelli previsti a causa delle modifiche apportate e rimuovere il test del fumo meno leggibile, poiché il test dell'unità scritto a mano ha una copertura completa di quel componente migliorato. Assicurati che la copertura del test non diminuisca, rimanga invariata o aumenti.

  5. Quando si correggono i bug, scrivere un test unitario fallito che espone prima il bug.


3

Vorrei iniziare questa risposta dicendo che i test unitari sono davvero importanti perché ti aiuteranno ad arrestare i bug prima che entrino in produzione.

Identificare le aree progetti / moduli in cui i bug sono stati reintrodotti. iniziare con quei progetti per scrivere test. Ha perfettamente senso scrivere test per nuove funzionalità e per la correzione di bug.

Vale la pena lo sforzo per una soluzione esistente in produzione?

Sì. Vedrai l'effetto dei bug che si abbassano e la manutenzione diventa più semplice

Meglio ignorare i test per questo progetto e aggiungerlo in una possibile riscrittura futura?

Consiglierei di iniziare se da adesso.

Cosa sarà più vantaggioso; trascorrere qualche settimana aggiungendo test o qualche settimana aggiungendo funzionalità?

Stai facendo la domanda sbagliata. Sicuramente, la funzionalità è più importante di ogni altra cosa. Ma piuttosto dovresti chiedere se passare qualche settimana ad aggiungere test renderà il mio sistema più stabile. Questo aiuterà il mio utente finale? Aiuterà un nuovo sviluppatore nel team a comprendere il progetto e anche a garantire che lui / lei non introduca un bug a causa della mancanza di comprensione dell'impatto complessivo di una modifica.


3

Sono molto affezionato a Refactor the Low-hanging Fruit come risposta alla domanda su dove iniziare il refactoring. È un modo per facilitare un design migliore senza mordere più di quanto si possa masticare.

Penso che la stessa logica si applichi al TDD - o solo ai test unitari: scrivi i test che ti servono, quando ne hai bisogno; scrivere test per nuovo codice; scrivere test per i bug così come appaiono. Sei preoccupato di trascurare le aree più difficili da raggiungere della base di codice, ed è certamente un rischio, ma come un modo per iniziare: iniziare! Puoi mitigare il rischio lungo la strada con strumenti di copertura del codice e il rischio non è (secondo me) così grande, comunque: se stai coprendo i bug, coprendo il nuovo codice, coprendo il codice che stai guardando , quindi stai coprendo il codice che ha il maggior bisogno di test.


2
  • sì. quando inizi ad aggiungere nuove funzionalità può causare alcune vecchie modifiche al codice e di conseguenza è fonte di potenziali bug.
  • (vedi il primo) prima di iniziare ad aggiungere nuove funzionalità tutto (o quasi) il codice (idealmente) dovrebbe essere coperto da unit test.
  • (vedi il primo e il secondo) :). una nuova grandiosa funzionalità può "distruggere" il vecchio codice funzionante.

2

Sì, è possibile: prova ad assicurarti che tutto il codice da cui scrivi abbia un test in atto.

Se il codice che è già in atto deve essere modificato e può essere testato, quindi farlo, ma è meglio non essere troppo vigoroso nel tentativo di mettere in atto i test per un codice stabile. Questo genere di cose tende ad avere un effetto a catena e può andare fuori controllo.


2

Vale la pena lo sforzo per una soluzione esistente in produzione?
Sì. Ma non è necessario scrivere tutti i test unitari per iniziare. Aggiungili uno per uno.

Meglio ignorare i test per questo progetto e aggiungerlo in una possibile riscrittura futura?
No. La prima volta che aggiungi codice che interrompe la funzionalità, te ne pentirai.

Cosa sarà più vantaggioso; trascorrere qualche settimana aggiungendo test o qualche settimana aggiungendo funzionalità?
Per le nuove funzionalità (codice) è semplice. Scrivi prima il test unitario e poi la funzionalità. Per il vecchio codice decidi sulla strada. Non devi avere tutti i test unitari in atto ... Aggiungi quelli che ti fanno più male non avendo ... Il tempo (e gli errori) diranno su quale devi concentrarti;)


2

Aggiornare

6 anni dopo la risposta originale, ho un approccio leggermente diverso.

Penso che abbia senso aggiungere test unitari a tutto il nuovo codice che scrivi - e quindi refactificare i punti in cui apporti le modifiche per renderle testabili.

Scrivere test in una volta sola per tutto il codice esistente non sarà di aiuto, ma non ha senso anche scrivere test per il nuovo codice che scrivi (o le aree che modifichi). L'aggiunta di test durante il refactoring / aggiunta di cose è probabilmente il modo migliore per aggiungere test e rendere il codice più gestibile in un progetto esistente senza test.

Prima risposta

Ho intenzione di alzare alcune sopracciglia qui :)

Prima di tutto qual è il tuo progetto - se è un compilatore o una lingua o un framework o qualsiasi altra cosa che non cambierà funzionalmente per molto tempo, allora penso che sia assolutamente fantastico aggiungere test unitari.

Tuttavia, se stai lavorando su un'applicazione che probabilmente richiederà cambiamenti di funzionalità (a causa della modifica dei requisiti), non ha senso fare uno sforzo in più.

Perché?

  1. I test unitari coprono solo i test del codice - indipendentemente dal fatto che il codice faccia quello per cui è progettato - non sostituiscono i test manuali che devono comunque essere eseguiti (per scoprire bug funzionali, problemi di usabilità e tutti gli altri tipi di problemi)

  2. I test unitari costano tempo! Ora da dove vengo, questo è un bene prezioso - e le aziende generalmente scelgono una migliore funzionalità rispetto a una suite di test completa.

  3. Se la tua applicazione è anche lontanamente utile per gli utenti, richiederanno modifiche - quindi avrai versioni che faranno le cose meglio, più velocemente e probabilmente faranno nuove cose - ci potrebbe essere anche un sacco di refactoring man mano che il tuo codice cresce. Mantenere una suite di test di unità completamente sviluppata in un ambiente dinamico è un mal di testa.

  4. I test unitari non influenzeranno la qualità percepita del tuo prodotto, ma la qualità che l'utente vede. Certo, i tuoi metodi potrebbero funzionare esattamente come il primo giorno, l'interfaccia tra il livello di presentazione e il livello aziendale potrebbe essere incontaminata - ma indovina un po '? All'utente non importa! Ottieni alcuni tester reali per testare la tua applicazione. E il più delle volte, questi metodi e interfacce devono cambiare comunque, prima o poi.

Cosa sarà più vantaggioso; trascorrere qualche settimana aggiungendo test o qualche settimana aggiungendo funzionalità? - Ci sono un sacco di cose che puoi fare meglio che scrivere test - Scrivi nuove funzionalità, migliora le prestazioni, migliora l'usabilità, scrivi meglio manuali di aiuto, risolve bug in sospeso, ecc. Ecc.

Ora non fraintendetemi: se siete assolutamente sicuri che le cose non cambieranno per i prossimi 100 anni, andate avanti, buttatevi fuori e scrivete quei test. I test automatici sono un'ottima idea anche per le API, in cui non si desidera assolutamente violare il codice di terze parti. Ovunque, è solo qualcosa che mi fa spedire più tardi!



Preferiresti "correggere i bug in sospeso" o impedire che si presentassero? Un corretto test delle unità consente di risparmiare tempo riducendo al minimo il tempo dedicato alla correzione di errori.
Ihor Kaharlichenko,

Questo è un mito. Se mi stai dicendo che i test di unità automatizzati sono una sostituzione per i test manuali, allora ti sbagli seriamente, seriamente. E cosa registrano i tester manuali, se non i bug?
Roopesh Shenoy,

E sì, non fraintendetemi - Non sto dicendo che i test unitari siano uno spreco assoluto - il punto è considerare il tempo necessario per scriverli e i motivi per cui potrebbero cambiare quando cambiate il vostro prodotto, ripagano davvero. per me ho provato entrambe le parti e la risposta è stata no, non ripagano abbastanza velocemente.
Roopesh Shenoy,

1

È improbabile che tu abbia mai una copertura significativa dei test, quindi devi essere tattico su dove aggiungere i test:

  • Come hai detto, quando trovi un bug, è un buon momento per scrivere un test (per riprodurlo), e quindi correggere il bug. Se vedi che il test riproduce il bug, puoi essere sicuro che sia un buon test. Dato che una parte così grande di bug sono regressioni (50%?), Quasi sempre vale la pena scrivere test di regressione.
  • Quando ti immergi in un'area di codice per modificarlo, è un buon momento per scrivere dei test attorno ad esso. A seconda della natura del codice, sono appropriati diversi test. Un buon consiglio è disponibile qui .

OTOH, non vale la pena limitarsi a scrivere test su codice di cui le persone sono felici, specialmente se nessuno lo modificherà. Semplicemente non aggiunge valore (tranne forse capire il comportamento del sistema).

In bocca al lupo!



1

Se fossi al tuo posto, probabilmente prenderei un approccio esterno, iniziando con test funzionali che esercitano l'intero sistema. Vorrei provare a documentare nuovamente i requisiti del sistema usando un linguaggio di specifica BDD come RSpec, e quindi scrivere test per verificare tali requisiti automatizzando l'interfaccia utente.

Quindi farei uno sviluppo guidato da difetti per i bug appena scoperti, scrivendo test unitari per riprodurre i problemi e lavorando sui bug fino al superamento dei test.

Per le nuove funzionalità, mi atterrei all'approccio outside-in: Inizia con le funzionalità documentate in RSpec e verificate automatizzando l'interfaccia utente (che ovviamente fallirà inizialmente), quindi aggiungere test unitari più dettagliati man mano che l'implementazione procede.

Non sono un esperto del processo, ma da quella poca esperienza che posso fare posso dirti che BDD tramite test UI automatizzati non è facile, ma penso che valga la pena, e probabilmente produrrebbe il massimo beneficio nel tuo caso.


1

Non sono un esperto TDD esperto in ogni caso, ma ovviamente direi che è incredibilmente importante testare l'unità il più possibile. Dato che il codice è già in atto, vorrei iniziare mettendo in atto una sorta di automazione di unit test. Uso TeamCity per eseguire tutti i test nei miei progetti e ti dà un bel riassunto di come hanno fatto i componenti.

Con quello in atto, passerei a quei componenti di tipo logico di business davvero critici che non possono fallire. Nel mio caso, ci sono alcuni problemi di trigometria di base che devono essere risolti per vari input, quindi ne provo il diavolo. La ragione per cui lo faccio è che quando sto bruciando l'olio di mezzanotte, è molto facile perdere tempo a scavare fino a profondità di codice che non hanno davvero bisogno di essere toccati, perché sai che sono testati per tutti i possibili input (nel mio caso, c'è un numero finito di input).

Ok, quindi ora spero ti senti meglio con quei pezzi critici. Invece di sedermi e battere tutti i test, li attaccherei mentre salgono. Se colpisci un bug che è un vero PITA da correggere, scrivi i test unitari e togliili di mezzo.

Ci sono casi in cui scoprirai che i test sono difficili perché non puoi creare un'istanza di una determinata classe dal test, quindi devi prenderla in giro. Oh, ma forse non puoi deriderlo facilmente perché non hai scritto su un'interfaccia. Prendo questi scenari "whoops" come un'opportunità per implementare detta interfaccia, perché, beh, è ​​una buona cosa.

Da lì, otterrei il tuo server di build o qualsiasi altra automazione che hai in atto configurato con uno strumento di copertura del codice. Creano cattivi grafici a barre con grandi zone rosse in cui la copertura è scarsa. Ora una copertura del 100% non è il tuo obiettivo, né una copertura del 100% significherebbe necessariamente che il tuo codice è a prova di proiettile, ma la barra rossa mi motiva sicuramente quando ho tempo libero. :)


1

Ci sono così tante buone risposte, quindi non ripeterò il loro contenuto. Ho controllato il tuo profilo e sembra che tu sia lo sviluppatore di C # .NET. Per questo motivo sto aggiungendo un riferimento al progetto Microsoft PEX e Moles che può aiutarti con i test unitari di generazione automatica di codice legacy. So che l'autogenerazione non è il modo migliore, ma almeno è il modo di iniziare. Leggi questo articolo molto interessante della rivista MSDN sull'uso di PEX per il codice legacy .


1

Suggerisco di leggere un brillante articolo di un ingegnere TopTal, che spiega da dove iniziare ad aggiungere i test: contiene molti calcoli matematici, ma l'idea di base è:

1) Misura l' afferent Coupling (CA) del tuo codice (quanto una classe viene utilizzata da altre classi, il che significa che romperla causerebbe un danno diffuso)

2) Misura la complessità ciclica del tuo codice (CC) (maggiore complessità = maggiore cambiamento di rottura)

È necessario identificare le classi con CA e CC elevati, ovvero avere una funzione f (CA, CC) e le classi con le differenze minime tra le due metriche devono avere la massima priorità per la copertura del test.

Perché? Perché una CA alta ma classi CC molto basse sono molto importanti ma è improbabile che si rompano. D'altra parte, una CA bassa ma una CC alta possono rompersi, ma causerà meno danni. Quindi vuoi bilanciare.


0

Dipende ...
È bello avere test unitari, ma è necessario considerare chi sono i tuoi utenti e cosa sono disposti a tollerare per ottenere un prodotto più privo di bug. Inevitabilmente, rifattorizzando il codice che al momento non ha test unitari, introdurrai dei bug e molti utenti troveranno difficile capire che stai rendendo temporaneamente il prodotto più difettoso per renderlo meno difettoso a lungo termine. Alla fine sono gli utenti che avranno l'ultima parola ...


-2

Sì. No. Aggiunta di test.

Andare verso un approccio più TDD in realtà informerà meglio i tuoi sforzi per aggiungere nuove funzionalità e rendere molto più facile il test di regressione. Controlla!

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.