Scrivere test per il codice esistente


68

Supponiamo che uno avesse un programma relativamente grande (diciamo 900k SLOC in C #), tutti commentati / documentati a fondo, ben organizzati e funzionanti. L'intera base di codice è stata scritta da un singolo sviluppatore senior che non è più con l'azienda. Tutto il codice è testabile così com'è e IoC è usato dappertutto - tranne per qualche strana ragione per cui non hanno scritto alcun test unitario. Ora, la tua azienda desidera ramificare il codice e desidera che vengano aggiunti test unitari per rilevare quando le modifiche interrompono la funzionalità principale.

  • Aggiungere test è una buona idea?
  • Se è così, come si potrebbe iniziare su qualcosa del genere?

MODIFICARE

OK, quindi non mi aspettavo risposte che facessero buoni argomenti per conclusioni opposte. Il problema potrebbe essere fuori dalle mie mani comunque. Ho letto anche le "domande duplicate" e il consenso generale è che "scrivere i test è buono" ... sì, ma non troppo utile in questo caso particolare.

Non credo di essere solo qui a contemplare la scrittura di test per un sistema legacy. Terrò le metriche su quanto tempo è trascorso e quante volte i nuovi test rilevano problemi (e quante volte non lo fanno). Tornerò e lo aggiornerò tra circa un anno con i miei risultati.

CONCLUSIONE

Quindi risulta sostanzialmente impossibile aggiungere un unit test al codice esistente con una parvenza di ortodossia. Una volta che il codice funziona, ovviamente, non è possibile eseguire il red-light / green-light dei test, di solito non è chiaro quali comportamenti sono importanti da testare, non è chiaro da dove iniziare e certamente non è chiaro quando si è terminati. In realtà anche porre questa domanda manca in primo luogo al punto principale di scrivere test. Nella maggior parte dei casi ho trovato in realtà più semplice riscrivere il codice usando TDD piuttosto che decifrare le funzioni previste e aggiungere retroattivamente i test unitari. Quando si risolve un problema o si aggiunge una nuova funzionalità, è una storia diversa e credo che sia il momento di aggiungere test unitari (come alcuni hanno sottolineato di seguito). Alla fine la maggior parte del codice viene riscritta, spesso prima del previsto - adottando questo approccio


10
L'aggiunta di test non romperà il codice esistente.
Dan Pichelman,

30
@DanPichelman Non hai mai sperimentato uno schroedinbug - "Un bug di progettazione o implementazione in un programma che non si manifesta fino a quando qualcuno che legge la fonte o usa il programma in modo insolito nota che non avrebbe mai dovuto funzionare, a quel punto il programma prontamente smette di funzionare per tutti fino a quando non verrà risolto. "

8
@MichaelT Ora che me lo dici, penso di aver visto uno o due di quelli. Il mio commento avrebbe dovuto leggere "L'aggiunta di test di solito non romperà il codice esistente". Grazie
Dan Pichelman,

3
Scrivi solo prove intorno a cose che intendi modificare o modificare.
Steven Evers,

3
Controlla il libro "Lavorare efficacemente con il codice legacy": amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/… , Michael Feathers suggerisce di scrivere i test prima di modificare qualsiasi codice legacy.
Skarab,

Risposte:


68

Mentre i test sono una buona idea, l'intenzione era che il programmatore originale li costruisse mentre stava costruendo l'applicazione per acquisire la sua conoscenza di come dovrebbe funzionare il codice e cosa potrebbe rompersi, che sarebbe poi stato trasferito a te.

Nel prendere questo approccio, c'è un'alta probabilità che tu stia scrivendo i test che hanno meno probabilità di rompersi e perdi la maggior parte dei casi limite che sarebbero stati scoperti durante la creazione dell'applicazione.

Il problema è che la maggior parte del valore deriverà da quei "trucchi" e da situazioni meno ovvie. Senza questi test, la suite di test perde praticamente tutta la sua efficacia. Inoltre, la società avrà un falso senso di sicurezza attorno alla propria applicazione, in quanto non sarà significativamente più a prova di regressione.

Tipicamente, il modo di gestire questo tipo di base di codice è scrivere test per il nuovo codice e per il refactoring del vecchio codice fino a quando la base di codice legacy non viene interamente rifattorizzata.

Anche vedere .


11
Ma, anche senza TDD, i test unitari sono utili per il refactoring.
pdr,

1
Se il codice continua a funzionare bene, non è un problema, ma la cosa migliore da fare sarebbe testare l'interfaccia su codice legacy ogni volta che scrivi qualcosa che si basa sul suo comportamento.
deworde,

1
Questo è il motivo per cui si misura la copertura del test . Se il test per una determinata sezione non copre tutti gli if e gli elses e tutti i casi limite, non è possibile eseguire il refactoring sicuro di quella sezione. La copertura ti dirà se tutte le linee vengono colpite, quindi il tuo obiettivo è quello di aumentare la copertura il più possibile prima del refactoring.
Rudolf Olah,

3
Un grave difetto di TDD è che, sebbene la suite possa essere eseguita, per lo sviluppatore che non ha familiarità con la base di codice, questo è un falso senso di sicurezza. BDD è molto meglio in questo senso poiché l'output è l' intenzione del codice in un inglese semplice.
Robbie Dee,

3
Proprio come menzionare che la copertura del codice al 100% non significa che il codice funzioni correttamente il 100% delle volte. Puoi testare ogni riga di codice per il metodo, ma solo perché funziona con value1 non significa che funzioni con value2.
Ryanzec,

35

Sì, aggiungere test è sicuramente una buona idea.

Dici che è ben documentato e che ti mette in una buona posizione. Prova a creare test usando quella documentazione come guida, concentrandoti su parti del sistema che sono critiche o soggette a frequenti cambiamenti.

Inizialmente, la semplice dimensione della base di codice sembrerà probabilmente schiacciante rispetto alla piccola quantità di test, ma non esiste un approccio big bang e iniziare da qualche parte è più importante che angosciarsi su quale sarà l'approccio migliore.

Consiglierei il libro di Michael Feathers, Lavorare efficacemente con il codice legacy , per alcuni buoni consigli dettagliati.


10
+1. Se scrivi i test in base alla documentazione, scoprirai eventuali discrepanze tra il codice di lavoro e la documentazione, e questo è prezioso.
Carl Manaster,

1
Un altro motivo per aggiungere test: quando viene trovato un bug, puoi facilmente aggiungere un caso di test per futuri test di regressione.

Questa strategia è sostanzialmente quella delineata nel corso (gratuito) online edX CS169.2x nel capitolo sul codice legacy. Come dicono gli insegnanti: "Stabilire la verità di base con i test di caratterizzazione" nel capitolo 9 del libro: beta.saasbook.info/table-of-contents
MGF

21

Non tutti i test unitari hanno lo stesso vantaggio. Il vantaggio di un test unitario viene quando fallisce. Meno è probabile che fallisca, meno è benefico. È più probabile che il codice nuovo o modificato di recente contenga bug rispetto al codice raramente modificato che è ben testato in produzione. Pertanto, i test unitari su codice nuovo o modificato di recente hanno maggiori probabilità di essere più vantaggiosi.

Non tutti i test unitari hanno lo stesso costo. È molto più facile testare il codice banale che hai progettato tu stesso oggi rispetto al codice complesso che qualcun altro ha progettato molto tempo fa. Inoltre, i test durante lo sviluppo di solito fanno risparmiare tempo di sviluppo. Sul codice legacy tale risparmio sui costi non è più disponibile.

In un mondo ideale, avresti tutto il tempo che ti serve per testare il codice legacy unitario, ma nel mondo reale, a un certo punto, è logico che i costi dell'aggiunta dei test unitari al codice legacy supereranno i benefici. Il trucco è identificare quel punto. Il controllo della versione può aiutarti mostrando l'ultimo codice modificato e più frequentemente modificato e puoi iniziare mettendo quelli sotto test unitario. Inoltre, quando si apportano modifiche in futuro, mettere tali modifiche e il codice strettamente correlato sotto test unitario.

Seguendo questo metodo, alla fine avrai una copertura abbastanza buona nelle aree più vantaggiose. Se invece trascorri mesi a mettere in atto i test unitari prima di riprendere le attività che generano entrate, questa potrebbe essere una decisione desiderabile di manutenzione del software, ma è una pessima decisione commerciale.


3
Se il codice raramente fallisce, si potrebbe impiegare molto tempo e fatica a trovare problemi arcani che non potrebbero mai verificarsi nella vita reale. D'altra parte, se il codice è difettoso e soggetto a errori, probabilmente potresti iniziare a testare ovunque e trovare immediatamente i problemi.
Robbie Dee,

8

Aggiungere test è una buona idea?

Assolutamente, anche se trovo un po 'difficile credere che il codice sia pulito, funzioni bene e usi tecniche moderne e semplicemente non abbia test unitari. Sei sicuro che non stiano seduti in una soluzione separata?

Ad ogni modo, se hai intenzione di estendere / mantenere il codice, i veri test unitari sono preziosi per quel processo.

Se è così, come si potrebbe iniziare su qualcosa del genere?

Un passo alla volta. Se non hai familiarità con i test unitari, allora impara un po '. Una volta che ti senti a tuo agio con i concetti, scegli una piccola sezione del codice e scrivi dei test per questo. Quindi il prossimo e il prossimo. La copertura del codice può aiutarti a trovare i punti che hai perso.

Probabilmente è meglio scegliere cose pericolose / rischiose / vitali da testare prima, ma potresti essere più efficace testare qualcosa di diretto per entrare prima in un groove, specialmente se tu / il team non sei abituato alla base di codice e / o all'unità test.


7
"Sei sicuro che non stiano seduti in una soluzione separata?" è un'ottima domanda Spero che l'OP non lo trascuri.
Dan Pichelman,

Sfortunatamente, sfortunatamente, l'applicazione è stata avviata molti anni fa proprio mentre TDD stava guadagnando trazione, quindi l'intento era di fare dei test ad un certo punto, ma per qualche motivo una volta avviato il progetto non ci sono mai riusciti.
Paolo

2
Probabilmente non ci sono mai riusciti perché ci sarebbe voluto del tempo extra che non ne valeva la pena. Un buon sviluppatore che lavora da solo può sicuramente creare un'applicazione pulita, chiara, ben organizzata e funzionante di dimensioni relativamente grandi senza alcun test automatizzato, e generalmente può farlo più velocemente e senza errori come con i test. Dal momento che è tutto nella loro testa, c'è una probabilità significativamente più bassa di bug o problemi organizzativi rispetto al caso di più sviluppatori che lo creano.
Ben Lee,

3

Sì, fare dei test è una buona idea. Aiuteranno a documentare il funzionamento della base di codice esistente come previsto e a rilevare eventuali comportamenti imprevisti. Anche se inizialmente i test falliscono, lasciarli e quindi refactificare il codice in un secondo momento in modo che passino e si comportino come previsto.

Inizia a scrivere test per classi più piccole (quelle che non hanno dipendenze e sono relativamente semplici) e passa a classi più grandi (quelle che hanno dipendenze e sono più complesse). Ci vorrà molto tempo, ma sii paziente e persistente in modo da poter coprire il più possibile la base di codice.


Aggiungeresti davvero test falliti a un programma che funziona bene (dice l'OP)?
MarkJ,

Sì, perché mostra che qualcosa non funziona come previsto e richiede un'ulteriore revisione. Ciò stimolerà la discussione e, si spera, correggerà eventuali equivoci o difetti precedentemente sconosciuti.
Bernard,

@Bernard - oppure, i test potrebbero rivelare il tuo malinteso su cosa dovrebbe fare il codice. I test scritti dopo il fatto corrono il rischio di non incapsulare correttamente le intenzioni originali.
Dan Pichelman,

@DanPichelman: D'accordo, ma questo non dovrebbe scoraggiare nessuno dallo scrivere alcun test.
Bernard,

Se non altro, indicherebbe che il codice non è stato scritto in modo difensivo.
Robbie Dee,

3

OK, ho intenzione di dare l'opinione contraria ....

L'aggiunta di test a un sistema funzionante esistente cambierà quel sistema, a meno che non lo sia, il sistema è tutto scritto pensando fin dall'inizio. Ne dubito, anche se è del tutto possibile che abbia una buona separazione di tutti i componenti con confini facilmente definibili in cui è possibile inserire le interfacce simulate. Ma se non lo è, allora dovrai apportare cambiamenti abbastanza significativi (relativamente parlando) che potrebbero spezzare le cose. Nel migliore dei casi, passerai un sacco di tempo a scrivere questi test, tempo che potrebbe essere meglio speso scrivendo un carico di documenti di progettazione dettagliati, documenti di analisi dell'impatto o documenti di configurazione della soluzione. Dopotutto, questo è il lavoro che il tuo capo vuole fare più dei test unitari. No?

Ad ogni modo, non aggiungerei alcun test unitario.

Mi concentrerei su strumenti di test esterni e automatizzati che ti offriranno una copertura ragionevole senza cambiare nulla. Quindi, quando vieni ad apportare modifiche ... è allora che puoi iniziare ad aggiungere unit test all'interno della base di codice.


2

Mi sono spesso imbattuto in questa situazione, ho ereditato una base di codice di grandi dimensioni senza una copertura di test adeguata o assente, e ora sono responsabile per l'aggiunta di funzionalità, la correzione di bug, ecc.

Il mio consiglio è di accertarmi e testare ciò che si aggiunge e, se si correggono i bug o si modificano i casi d'uso nel codice corrente, i test dell'autore quindi. Se devi toccare qualcosa, scrivi dei test a quel punto.

Quando ciò si interrompe è quando il codice esistente non è ben strutturato per i test unitari, quindi si dedica molto tempo al refactoring in modo da poter aggiungere test per modifiche minori.

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.