Quando è opportuno non testare l'unità?

130

Lavoro in una piccola azienda come sviluppatore solista. Infatti, sono l'unico sviluppatore dell'azienda. Ho diversi progetti (relativamente) di grandi dimensioni che ho scritto e gestito regolarmente, e nessuno di loro ha test per supportarli. All'inizio di nuovi progetti mi chiedo spesso se dovrei provare un approccio TDD. Sembra una buona idea, ma onestamente non posso mai giustificare il lavoro extra in questione.

Lavoro duro per essere lungimirante nel mio design. Mi rendo conto che sicuramente un giorno un altro sviluppatore dovrà mantenere il mio codice, o almeno risolverlo. Mantengo le cose il più semplici possibile e commento e documento cose che sarebbero difficili da comprendere. E il fatto è che questi progetti non sono così grandi o complicati che uno sviluppatore decente avrebbe difficoltà a comprenderli.

Molti degli esempi che ho visto dei test si riducono alle minuzie, coprendo tutti gli aspetti del codice. Dato che sono l'unico sviluppatore e sono molto vicino al codice nell'intero progetto, è molto più efficiente seguire uno schema di scrittura, quindi manualmente. Trovo anche che i requisiti e le funzionalità cambiano abbastanza frequentemente che il mantenimento dei test aggiungerebbe una notevole quantità di trascinamento su un progetto. Tempo che altrimenti potrebbe essere impiegato per risolvere le esigenze aziendali.

Quindi finisco sempre con la stessa conclusione. Il ritorno sull'investimento è troppo basso.

Di tanto in tanto ho impostato alcuni test per assicurarmi di aver scritto correttamente un algoritmo, come calcolare il numero di anni in cui qualcuno è stato nella società in base alla data di assunzione. Ma da un punto di vista della copertura del codice ho coperto circa l'1% del mio codice.

Nella mia situazione, troveresti ancora un modo per rendere la sperimentazione unitaria una pratica regolare, o sono giustificato nell'evitare quel sovraccarico?

UPDATE: alcune cose sulla mia situazione che ho tralasciato: i miei progetti sono tutte applicazioni web. Per coprire tutto il mio codice, dovrei utilizzare i test dell'interfaccia utente automatizzati, e questa è un'area in cui ancora non vedo un grande vantaggio rispetto ai test manuali.

    
posta Ken Pespisa 08.04.2011 - 17:41
fonte

14 risposte

80

A lot of the examples I've seen of tests get down to the minutiae, covering all facets of the code.

E allora? Non devi testare tutto . Solo le cose rilevanti.

Since I'm the only developer and I'm very close to the code in the entire project, it is much more efficient to follow a write-then-manually-test pattern.

Questo è in realtà falso. Non è più efficiente. È davvero solo un'abitudine.

Ciò che altri sviluppatori solisti fanno è scrivere uno schizzo o un contorno, scrivere i casi di test e quindi compilare il profilo con il codice finale.

È molto, molto efficiente.

I also find requirements and features change frequently enough that maintaining tests would add a considerable amount of drag on a project.

Anche questo è falso. I test non sono la resistenza. Le modifiche ai requisiti sono il trascinamento.

Devi correggere i test per riflettere i requisiti. Se le loro minuzie, o di alto livello; scritto per primo o scritto per ultimo.

Il codice non viene eseguito fino al passaggio dei test. Questa è l'unica verità universale del software.

Puoi avere un test di accettazione "qui è" limitato.

Oppure puoi avere alcuni test unitari.

Oppure puoi averli entrambi.

Ma non importa quello che fai, c'è sempre un test per dimostrare che il software funziona.

Suggerirei che un po 'di formalità e una bella suite di strumenti di test unitari rendono il test molto più utile.

    
risposta data 08.04.2011 - 18:06
fonte
102

Immagina di avere una serie di test che potrebbero essere eseguiti in un attimo e accendere una luce verde o rossa. Immagina che questa suite di test abbia provato tutto ! Immagina che tutto quello che dovevi fare per eseguire la suite di test era digitare ^ T. Che potere ti darebbe questo?

Potresti apportare una modifica al codice senza paura di rompere qualcosa? Potresti aggiungere una nuova funzione senza timore di rompere una vecchia funzionalità? Potresti pulire velocemente il codice disordinato senza temere di fare danni?

Sì, potresti fare tutte quelle cose! E cosa succederebbe al tuo codice nel tempo? Sarebbe più pulito e pulito perché non ci sarebbero rischi per la sua pulizia.

Immaginiamo di avere una piccola fata sulla tua spalla. Ogni volta che scrivevi una riga di codice, la fata aggiungeva qualcosa alla suite di test che testava che quella linea di codice faceva ciò che intendeva fare. Quindi ogni paio di secondi potresti colpire ^ T e vedere che l'ultima riga di codice che hai scritto ha funzionato.

Quanto debugging pensi che dovresti fare?

Se sembra fantastico, hai ragione. Ma la realtà non è molto diversa. Sostituisci l'occhio con pochi secondi, e la fata con la disciplina TDD, e hai praticamente capito.

Diciamo che stai tornando a un sistema che hai costruito un anno fa e hai dimenticato come creare uno degli oggetti centrali. Ci sono prove che creano quell'oggetto in ogni modo in cui può essere creato. Puoi leggere quei test e fare jogging nella tua memoria. Hai bisogno di chiamare un'API? Esistono test che chiamano quell'API in tutti i modi in cui possono essere chiamati. Questi test sono piccoli documenti , scritti in una lingua che tu capisci. Sono completamente non ambigui. Sono così formali che eseguono. E non possono uscire dalla sincronizzazione con l'applicazione!

Non vale l'investimento? Mi stai prendendo in giro! Come potrebbe qualcuno NON volere quella suite di test? Fatti un favore e smettila di cavillare per la stupidità. Impara a fare bene il TDD e osserva quanto sei più veloce e quanto è più pulito il tuo codice.

    
risposta data 09.04.2011 - 01:04
fonte
32

L'errore che stai facendo è che stai vedendo i test come un investimento nel tempo senza un ritorno immediato. Non funziona necessariamente così.

Innanzitutto, scrivere test veramente ti concentra su ciò che questa parte del codice deve fare.

In secondo luogo eseguirli rivela bug che verrebbero altrimenti sottoposti a test.

In terzo luogo eseguirli a volte mostra bug che altrimenti non verrebbero sottoposti a test e poi ti morderebbero davvero in culo in produzione.

In quarto luogo, se si verifica un errore con un sistema in esecuzione e si crea un test unitario, non sarà possibile reintrodurlo successivamente. Questo può essere un grande aiuto. Gli errori introdotti sono comuni e molto fastidiosi.

Quinto se hai bisogno di passare il codice a qualcun altro, una suite di test renderà la vita molto più facile. Inoltre, se hai ignorato un progetto e ti ritorni dopo alcuni anni, non sarai più così vicino e ti sarà utile anche.

La mia esperienza è consistita nel fatto che attraverso lo sviluppo di un progetto, avere test di unità decenti ha sempre reso il processo più veloce e più affidabile.

    
risposta data 08.04.2011 - 17:58
fonte
31

I ragazzi di JUnit (Java Test Framework) hanno una filosofia che se è troppo semplice da testare, non testarlo . Consiglio vivamente di leggere le loro Domande frequenti sulle best practice , in quanto è piuttosto pragmatico.

TDD è un processo diverso di scrittura del software. La premessa di base del collaudo unitario è che trascorrerete meno tempo nel debugger attraverso il codice, e più rapidamente capire se il vostro codice cambia casualmente qualcos'altro nel sistema. Questo si adatta a TDD. Il ciclo TDD è come questo:

  1. Scrivi un test
  2. Guarda che fallisce (dimostra che hai qualcosa da fare)
  3. Scrivi solo ciò che è necessario per superare il test - non di più.
  4. Guarda passare (yay!)
  5. Refactor (renderlo migliore)
  6. Lavare, risciacquare e ripetere

Ciò che è meno ovvio nell'applicare TDD è che cambia il modo in cui il tuo codice di scrittura . Costringendoti a pensare a come testare / convalidare che il codice funzioni, stai scrivendo codice testabile. E dal momento che stiamo parlando di test unitari, questo di solito significa che il tuo codice diventa più modulare. Per me, il codice modulare e testabile è un grande vantaggio.

Ora, hai bisogno di testare cose come le proprietà di C #? Immagina una proprietà definita in questo modo:

bool IsWorthTesting {get; set;}

La risposta sarebbe "no", non vale la pena di provarla, perché a questo punto si sta testando la funzionalità della lingua. Confida solo che i ragazzi della piattaforma C # hanno capito bene. Inoltre, se fallisce, cosa potrebbe fare per risolverlo?

Inoltre, scoprirai che ci sono alcune parti del tuo codice che molto bene saranno troppo difficili da testare correttamente. Ciò significa che non farlo, ma assicurati di testare il codice che usa / è usato dal problema difficile:

  • Eccezioni verificate che possono verificarsi solo se un'installazione è andata a male. Java ne ha una tonnellata. Ti viene richiesto di scrivere un blocco catch o dichiarare l'eccezione verificata anche se non può in alcun modo fallire senza aver hackerato i file installati.
  • Interfacce utente. Trovare il controllo sotto test e invocare gli eventi giusti per simulare le azioni dell'utente è molto problematico e in alcuni casi impossibile. Tuttavia, se si utilizza il modello Modello / Visualizza / Controller, è possibile assicurarsi che il modello e i controllori siano testati e lasciare la parte vista a test manuale.
  • Interazioni client / server. Questo non è più un test di unità e ora è un test di integrazione . Scrivi tutte le parti che vanno a inviare e ricevere messaggi sul filo, ma in realtà non andare oltre il filo. Un buon approccio è quello di ridurre la responsabilità del codice che effettivamente parla sul filo alle comunicazioni non elaborate. Nel codice di test dell'unità, mock oggetto di comunicazione per assicurarsi che i servizi si comportino come previsto.

Che ci crediate o no, TDD ti aiuterà a cadere in un ritmo di sviluppo sostenibile. Non è a causa della magia, ma piuttosto perché hai un ciclo di feedback serrato e sei in grado di cogliere rapidamente errori stupidi. Il costo di correggere quegli errori è essenzialmente costante (almeno per scopi di pianificazione) perché i piccoli errori non crescono mai come grandi errori. Confrontalo con la natura esplosiva del code binge / debug purge sprint.

    
risposta data 08.04.2011 - 18:44
fonte
23

Devi bilanciare il costo del test con il costo dei bug.

Scrivere un test di 10 righe per una funzione che apre un file, in cui l'errore è "il file non trovato" è inutile.

Una funzione che fa qualcosa di complesso in una struttura dati complessa - quindi ovviamente sì.

Il trucchetto è intermedio. Ma ricorda che il valore reale dei test unitari non sta testando la particolare funzione, ma sta testando le interazioni difficili tra di loro. Quindi un test unitario che rileva che un cambiamento in un bit di codice, interrompe alcune funzioni in un modulo diverso a 1000 linee di distanza, vale il suo peso nel caffè.

    
risposta data 08.04.2011 - 17:54
fonte
22

Il test è il gioco d'azzardo.

Creare un test è una scommessa sul fatto che il costo dei bug in un'unità che si verificano e non li cattura con quel test (ora e durante tutte le future revisioni del codice) è maggiore del costo di sviluppo del test. I costi di sviluppo dei test includono cose come il libro paga per l'ingegneria di test aggiunta, il time-to-market aggiunto, i costi di opportunità persi da non codificare altre cose e così via.

Come ogni scommessa, a volte vinci, a volte perdi.

Qualche software in ritardo con molti meno bug vince su cose veloci ma buggy che arrivano prima sul mercato. A volte il contrario. Devi guardare le statistiche nel tuo campo specifico e quanto la gestione vuole scommettere.

Alcuni tipi di bug potrebbero essere così improbabili da essere realizzati, o per uscire da qualsiasi test preliminare di sanità mentale, in quanto statisticamente non vale la pena dedicare del tempo a creare test specifici aggiuntivi. Ma a volte il costo di un bug è così grande (medico, nucleare, ecc.) Che un'azienda deve fare una scommessa perdente (simile all'acquisto di un'assicurazione). Molte app non hanno un costo di fallimento così alto, e quindi non hanno bisogno della copertura assicurativa antieconomica più elevata. Altri lo fanno.

    
risposta data 26.04.2011 - 21:30
fonte
10

Il mio consiglio è di testare solo il codice che si desidera funzionare correttamente.

Non testare il codice che vuoi essere bacato e causare problemi per te.

    
risposta data 09.04.2011 - 07:45
fonte
8

I often wonder if I should try a TDD approach. It sounds like a good idea, but I honestly can never justify the extra work involved.

TDD e Unit Testing non sono la stessa cosa.

Puoi scrivere il codice, quindi aggiungere i test unitari più tardi. Questo non è TDD, ed è un lavoro extra.

TDD è la pratica della codifica in un loop di Red Light. Luce verde. Iterazioni refactoring

Significa scrivere test per il codice che non esiste ancora, guardare i test fallire, correggere il codice per far funzionare i test, quindi rendere il codice "giusto". Questo ti fa risparmiare molto lavoro

Uno dei vantaggi di TDD è che riduce la necessità di pensare alle curiosità. Le cose come errori fuori dalla porta scompaiono. Non devi andare a caccia attraverso la documentazione dell'API per scoprire se la lista che ritorna inizia da 0 o 1, fallo e basta.

    
risposta data 08.04.2011 - 18:08
fonte
3

Ho lavorato su un sistema in cui abbiamo testato quasi tutto. Le esecuzioni notevoli da testare erano il codice di output PDF e XLS.

Perché? Siamo stati in grado di testare le parti che hanno raccolto i dati e costruito il modello utilizzato per creare l'output. Siamo stati anche in grado di testare le parti che hanno capito quali parti del modello sarebbero andate ai file PDF. Non siamo stati in grado di verificare se il PDF sembrava ok perché era totalmente soggettivo. Non siamo stati in grado di verificare che tutte le parti di un PDF fossero leggibili da un utente tipico perché anche questo era soggettivo. O se la scelta tra barra e grafici a torta era corretta per il set di dati.

Se l'output sarà soggettivo, ci sono pochi test di unità che puoi fare ciò che vale lo sforzo.

    
risposta data 08.04.2011 - 22:39
fonte
2

Per molte cose, un test "write-then-manualmente" non richiede più tempo rispetto alla scrittura di un paio di test. Il risparmio di tempo deriva dalla possibilità di rieseguire questi test in qualsiasi momento.

Pensaci: se hai qualche copertura di funzione decente con i tuoi test (da non confondere con la copertura del codice), e diciamo che hai 10 funzioni - facendo clic su un pulsante significa che hai circa, 10 yous rifare i tuoi test ... mentre ti siedi e sorseggi il caffè.

Inoltre non avere per testare il minuto. Puoi scrivere test di integrazione che coprono le tue funzionalità se non vuoi scendere ai dettagli più nitidi ... IMO, alcuni test unitari ottengono test troppo dettagliati sulla lingua e sulla piattaforma, e non sul codice.

TL; DR Non è mai appropriato perché i vantaggi sono semplicemente troppo buoni.

    
risposta data 08.04.2011 - 17:52
fonte
2

Due ottime risposte che ho trovato sono qui:

  1. Quando test unitario vs test manuale
  2. Cosa non testare quando si tratta di unità test?

Le giustificazioni per evitare l'overhead percepito:

  • Immediato tempo / costi di risparmio per la tua azienda
  • Potenziale risparmio di tempo / costi nella risoluzione dei problemi / manutenibilità / estensione a lungo termine anche dopo che sei sparito.

Non vorresti lasciare un grande prodotto dalla tua parte come prova della qualità del tuo lavoro? Parlando in termini egoistici, non è meglio per te che lo fai?

    
risposta data 08.04.2011 - 17:53
fonte
1

Gli sviluppatori professionisti scrivono test di unità perché, a lungo termine, risparmiano tempo. Presto testerai il tuo codice prima o poi, e se non lo farai, i tuoi utenti, e se dovrai correggere bug in seguito, saranno più difficili da correggere e avranno più effetti di knock on.

Se stai scrivendo codice senza test e non hai bug allora va bene. Non credo che tu possa scrivere un sistema non banale con zero bug, quindi presumo che tu stia testandolo in un modo o nell'altro.

I test unitari sono anche fondamentali per prevenire regressioni quando si modifica o si raffigura il codice precedente. Non provano il tuo cambiamento non ha infranto il vecchio codice, ma ti danno molta fiducia (a patto che passino, ovviamente :))

Non tornerei indietro e scriverò un'intera serie di test per il codice che hai già spedito, ma la prossima volta che devi modificare una funzione suggerisco di provare a scrivere dei test per quel modulo o classe, assicurati la copertura al 70% + prima di applicare qualsiasi modifica. Vedi se ti aiuta.

Se ci provi e puoi dire onestamente che non è stato di aiuto, allora è abbastanza giusto, ma penso che ci siano abbastanza prove del settore che ti aiutano a renderlo almeno meritevole di sperimentare l'approccio.

    
risposta data 08.04.2011 - 18:00
fonte
1

Sembra che la maggior parte delle risposte siano pro-TDD, anche se la domanda non era la domanda su TDD ma sui test unitari in generale.

Non esiste una regola completamente oggettiva su cosa testare o meno il test unitario. Ma ci sono un paio di volte in cui sembra che molti programmatori non facciano un test unitario:

  1. Metodi privati

In base alla tua filosofia OOP, potresti creare metodi privati per separare le routine complesse dai tuoi metodi pubblici. Solitamente i metodi pubblici sono chiamati in molti posti diversi e vengono usati spesso, ei metodi privati sono realmente chiamati da uno o due metodi pubblici in una classe o in un modulo per qualcosa di molto specifico. Di solito è sufficiente scrivere test unitari per metodi pubblici ma non i metodi privati sottostanti che fanno accadere un po 'della magia. Se qualcosa va storto con un metodo privato, i test delle unità del metodo pubblico dovrebbero essere sufficienti per rilevare questi problemi.

  1. Le cose che sai già dovrebbero funzionare (o cose testate da qualcun altro)

Molti nuovi programmatori vanno contro questo quando stanno imparando a testare e pensano di dover testare ogni singola riga che viene eseguita. Se si utilizza una libreria esterna e la sua funzionalità è ben testata e documentata dai suoi autori, di solito è inutile testare la funzionalità specifica nei test unitari. Ad esempio, qualcuno potrebbe scrivere un test per assicurarsi che il loro modello ActiveRecord mantenga il valore corretto per un attributo con un callback "before_save" nel database, anche se tale comportamento è di per sé già accuratamente testato in Rails. Il metodo (i) che il callback sta chiamando, forse, ma non il comportamento di callback stesso. Qualsiasi problema di fondo con le librerie importate dovrebbe essere rivelato attraverso test di accettazione, piuttosto che test di unità.

Entrambi possono essere applicati indipendentemente dal fatto che stai facendo TDD o meno.

    
risposta data 04.03.2016 - 23:53
fonte
0

Ken, Io e molti altri sviluppatori là fuori sono arrivati alla stessa conclusione in cui voi diverse volte durante la nostra carriera.

La verità che ritengo tu possa trovare (come molti altri) è che l'investimento iniziale di test di scrittura per la tua applicazione può sembrare scoraggiante, ma se scritto bene e mirato alle parti corrette del tuo codice, possono davvero risparmia un sacco di tempo.

Il mio grosso problema era con i framework di test disponibili. Non ho mai avuto la sensazione di essere quello che stavo cercando, quindi ho appena ottenuto la mia soluzione molto semplice. Mi ha davvero aiutato a portarmi in giro per il "lato oscuro" dei test di regressione. Condividerò uno pseudo frammento di base di ciò che ho fatto qui, e spero che tu possa trovare una soluzione che funzioni per te.

public interface ITest {
    public string Name {
        get;
    }
    public string Description {
        get;
    }
    public List<ITest> SubTests {
        get;
    }
    public TestResult Execute();
}

public class TestResult {
    public bool Succesful {
        get;
        set;
    }

    public string ResultMessage {
        get;
        set;
    }

    private Dictionary<ITest, TestResult> subTestResults = new Dictionary<ITest, TestResult>();
    public Dictionary<ITest, TestResult> SubTestResults {
        get {
            return subTestResults;
        }
        set {
            subTestResults = value;
        }
    }
}

L'unica parte difficile dopo è capire quale livello di granularità pensi sia il miglior "bang for the buck" per qualunque progetto tu stia facendo.

Costruire una rubrica richiederà ovvi meno test rispetto a un motore di ricerca aziendale, ma i fondamentali non cambiano davvero.

Buona fortuna!

    
risposta data 08.04.2011 - 18:53
fonte

Leggi altre domande sui tag