È normale dedicare più tempo, se non di più, alla scrittura dei test rispetto al codice reale?


211

Trovo che i test siano molto più difficili e difficili da scrivere rispetto al codice reale che stanno testando. Non è insolito per me passare più tempo a scrivere il test rispetto al codice che sta testando.

È normale o sto facendo qualcosa di sbagliato?

Le domande “ Vale la pena testare le unità o sviluppare test-driven? "," Stiamo impiegando più tempo a implementare test funzionali che a implementare il sistema stesso, è normale? "E le loro risposte riguardano meglio se vale la pena testare (come in" dovremmo saltare del tutto i test di scrittura? "). Mentre sono convinto che i test siano importanti, mi chiedo se il mio passare più tempo sui test rispetto al codice reale sia normale o se sono solo io.

A giudicare dal numero di opinioni, risposte e voti della mia domanda ricevuta, posso solo supporre che sia una preoccupazione legittima che non viene affrontata in nessun'altra domanda sul sito Web.


20
Aneddotico, ma trovo che trascorro all'incirca tutto il tempo a scrivere test come a scrivere codice quando faccio TDD. È quando scivolo e scrivo i test dopo il fatto che passo più tempo sui test che sul codice.
RubberDuck,

10
Passi anche più tempo a leggere che a scrivere il tuo codice.
Thorbjørn Ravn Andersen,

27
Inoltre, i test sono codice reale. Non spedisci quella parte ai clienti.
Thorbjørn Ravn Andersen,

5
Idealmente, passerai più tempo a correre che a scrivere anche il tuo codice. (Altrimenti, faresti semplicemente il compito a mano.)
Joshua Taylor,

5
@RubberDuck: qui di fronte l'esperienza. A volte quando scrivo test dopo il fatto il codice e il design sono già abbastanza ordinati, quindi non ho bisogno di riscrivere troppo il codice e i test. Quindi ci vuole meno tempo per scrivere i test. Non è una regola, ma mi capita abbastanza spesso.
Giorgio,

Risposte:


205

Ricordo da un corso di ingegneria del software che uno dedica circa il 10% del tempo di sviluppo alla scrittura di nuovo codice e l'altro 90% a debug, test e documentazione.

Dato che i test unitari catturano il debug e testano lo sforzo nel codice (potenzialmente automatizzabile), sarebbe logico che ci vadano più sforzi; il tempo effettivo impiegato non dovrebbe essere molto più che il debug e il test che uno farebbe senza scrivere i test.

Infine, i test dovrebbero anche raddoppiare come documentazione! Si dovrebbero scrivere unit test nel modo in cui il codice deve essere usato; cioè i test (e l'uso) dovrebbero essere semplici, mettere le cose complicate nell'implementazione.

Se i tuoi test sono difficili da scrivere, allora il codice che testano è probabilmente difficile da usare!


4
Potrebbe essere un buon momento per capire perché il codice è così difficile da testare :) Prova a "srotolare" linee multifunzione complesse che non sono strettamente necessarie, come operatori binari / ternari nidificati in modo massiccio ... Odio davvero i binari inutili / operatore ternario che ha anche un operatore binario / ternario come uno dei percorsi ...
Nelson

53
Mi permetto di non essere d'accordo con l'ultima parte. Se stai mirando a una copertura di test unitaria molto elevata, devi coprire casi d'uso rari e talvolta totalmente contrari all'uso previsto del tuo codice. Scrivere test per questi casi angolari potrebbe essere solo la parte che richiede più tempo dell'intera attività.
otto,

L'ho già detto altrove, ma i test unitari tendono a durare molto più a lungo, perché la maggior parte del codice tende a seguire una sorta di modello "Principio di Pareto": puoi coprire circa l'80% della tua logica con circa il 20% del codice che impiega copre il 100% della tua logica (vale a dire che la copertura di tutti i casi limite richiede circa cinque volte più codice di test unitario). Naturalmente, a seconda del framework, è possibile inizializzare l'ambiente per più test, riducendo il codice complessivo necessario, ma anche questo richiede una pianificazione aggiuntiva. Avvicinarsi al 100% della fiducia richiede molto più tempo che semplicemente testare i percorsi principali.
phyrfox,

2
@phyrfox Penso che sia troppo cauto, è più simile a "l'altro 99% del codice è casi limite". Ciò significa che l'altro 99% dei test riguarda quei casi limite.
Móż,

@Nelson Concordo sul fatto che gli operatori ternari nidificati sono difficili da leggere, ma non credo che rendano i test particolarmente difficili (un buon strumento di copertura ti dirà se hai perso una delle possibili combinazioni). IMO, il software è difficile da testare quando è accoppiato troppo strettamente o dipende da dati cablati o dati non passati come parametro (ad es. Quando una condizione dipende dall'ora corrente e questo non viene passato come parametro). Questo non è direttamente correlato a quanto "leggibile" sia il codice, sebbene ovviamente tutte le altre cose siano uguali, il codice leggibile è migliore!
Andres F.

96

È.

Anche se si eseguono solo test unitari, non è insolito avere più codice nei test rispetto al codice effettivamente testato. Non c'è niente di sbagliato in questo.

Prendi in considerazione un codice semplice:

public void SayHello(string personName)
{
    if (personName == null) throw new NullArgumentException("personName");

    Console.WriteLine("Hello, {0}!", personName);
}

Quali sarebbero i test? Ci sono almeno quattro semplici casi da testare qui:

  1. Il nome della persona è null. L'eccezione viene effettivamente lanciata? Sono almeno tre righe di codice di prova da scrivere.

  2. Il nome della persona è "Jeff". Riceviamo una "Hello, Jeff!"risposta? Sono quattro righe di codice di prova.

  3. Il nome della persona è una stringa vuota. Quale output ci aspettiamo? Qual è l'output effettivo? Domanda a margine: corrisponde ai requisiti funzionali? Ciò significa che altre quattro righe di codice per l'unità di test.

  4. Il nome della persona è abbastanza breve per una stringa, ma troppo lungo per essere combinato con "Hello, "il punto esclamativo. Cosa succede? ¹

Ciò richiede molto codice di prova. Inoltre, i pezzi di codice più elementari richiedono spesso un codice di installazione che inizializza gli oggetti necessari per il codice in prova, che spesso porta anche alla scrittura di matrici e simulazioni, ecc.

Se il rapporto è molto grande, nel qual caso è possibile verificare alcune cose:

  • Esiste una duplicazione del codice durante i test? Il fatto che sia un codice di test non significa che il codice debba essere duplicato (copia-incolla) tra test simili: tale duplicazione renderà difficile il mantenimento di tali test.

  • Ci sono test ridondanti? Come regola generale, se si rimuove un test unitario, la copertura del ramo dovrebbe diminuire. In caso contrario, potrebbe indicare che il test non è necessario, poiché i percorsi sono già coperti da altri test.

  • Stai testando solo il codice che dovresti testare? Non è previsto il test del framework sottostante delle librerie di terze parti, ma esclusivamente il codice del progetto stesso.

Con i test del fumo, i test di sistema e di integrazione, i test funzionali e di collaudo e i test di stress e di carico, aggiungi ancora più codice di test, quindi avere quattro o cinque LOC di test per ogni LOC del codice reale non è qualcosa di cui dovresti preoccuparti.

Una nota su TDD

Se sei preoccupato per il tempo necessario per testare il tuo codice, è possibile che tu stia sbagliando, ovvero prima il codice, poi i test. In questo caso, TDD può aiutarti incoraggiandoti a lavorare in iterazioni di 15-45 secondi, passando da codice a test. Secondo i fautori di TDD, accelera il processo di sviluppo riducendo sia il numero di test che è necessario eseguire sia, soprattutto, la quantità di codice aziendale da scrivere e soprattutto riscrivere per i test.


¹ Sia n sia la lunghezza massima di una stringa . Possiamo chiamare SayHelloe passare per riferimento una stringa di lunghezza n - 1 che dovrebbe funzionare bene. Ora, al Console.WriteLinepasso, la formattazione dovrebbe finire con una stringa di lunghezza n + 8, che comporterà un'eccezione. Forse, a causa dei limiti di memoria, anche una stringa contenente n / 2 caratteri comporterà un'eccezione. La domanda che ci si dovrebbe porre è se questo quarto test è un test unitario (sembra uno, ma può avere un impatto molto maggiore in termini di risorse rispetto ai test unitari medi) e se verifica il codice effettivo o il framework sottostante.


5
Non dimenticare che una persona può avere anche il nome null. stackoverflow.com/questions/4456438/...
psatek

1
@JacobRaihle Suppongo che @MainMa significhi il valore di personNamefit in a string, ma il valore di personNameplus i valori concatenati traboccano string.
Woz

@JacobRaihle: ho modificato la mia risposta per spiegare questo punto. Vedi la nota a piè di pagina.
Arseni Mourzenko,

4
As a rule of thumb, if you remove a unit test, the branch coverage should decrease.Se scrivo tutti e quattro i test sopra menzionati e poi rimuovo il terzo test, la copertura diminuirà?
Vivek,

3
"abbastanza a lungo" → "troppo a lungo" (al punto 4)?
Paŭlo Ebermann,

59

Penso che sia importante distinguere tra due tipi di strategie di test: test unitari e test di integrazione / accettazione.

Sebbene in alcuni casi sia necessario il test unitario, spesso è irrimediabilmente superato. Ciò è aggravato da metriche insignificanti costrette agli sviluppatori, come "copertura al 100%". http://www.rbcs-us.com/documents/Why-Most-Unit-Testing-is-Waste.pdf fornisce un argomento convincente per questo. Considera i seguenti problemi con i test unitari aggressivi:

  • Un'abbondanza di test inutili che non documentano un valore aziendale, ma semplicemente esistono per avvicinarsi a quella copertura del 100%. Dove lavoro, dobbiamo scrivere unit test per fabbriche che non fanno altro che creare nuove istanze di classi. Non aggiunge alcun valore. O quei lunghi metodi .equals () generati da Eclipse - non è necessario testarli.
  • Per facilitare i test, gli sviluppatori suddividono gli algoritmi complessi in unità testabili più piccole. Sembra una vittoria, vero? Non se devi avere 12 classi aperte per seguire un percorso di codice comune. In questi casi, i test unitari possono effettivamente ridurre la leggibilità del codice. Un altro problema è che se si taglia il codice in pezzi troppo piccoli, si finisce con una grandezza di classi (o pezzi di codice) che sembrano non avere alcuna giustificazione oltre a essere un sottoinsieme di un altro pezzo di codice.
  • Il refactoring di codice altamente coperto può essere difficile, poiché è necessario mantenere anche l'abbondanza di test unitari che dipendono dal fatto che funzioni così . Ciò è aggravato dai test di unità comportamentali, in cui parte del test verifica anche l'interazione dei collaboratori di una classe (di solito derisi).

I test di integrazione / accettazione, d'altra parte, sono una parte estremamente importante della qualità del software e, nella mia esperienza, dovresti dedicare una notevole quantità di tempo a farli funzionare bene.

Molti negozi hanno bevuto il kool-aid TDD, ma come mostra il link sopra, un certo numero di studi dimostra che il suo beneficio è inconcludente.


10
+1 per il punto sul refactoring. Lavoravo su un prodotto legacy che era stato patchato e klug da oltre un decennio. Il solo tentativo di determinare le dipendenze per un particolare metodo potrebbe richiedere la parte migliore di una giornata, quindi provare a capire come deriderle potrebbe richiedere ancora più tempo. Non è insolito che una modifica a cinque righe richieda oltre 200 righe di codice di test e che richieda la parte migliore di una settimana.
TMN,

3
Questo. Nella risposta di MainMa il test 4 non dovrebbe essere eseguito (al di fuori di un contesto accademico), perché pensa a come ciò potrebbe accadere in pratica ... se il nome di una persona è vicino alla dimensione massima di una stringa qualcosa è andato storto. Non eseguire il test, nella maggior parte dei casi non esiste un percorso di codice per rilevarlo. La risposta appropriata è consentire al framework di eliminare l'eccezione di memoria insufficiente sottostante, poiché questo è il vero problema.
Móż,

3
Ti sto incoraggiando fino a quando "non c'è bisogno di testare quei lunghi .equals()metodi generati da Eclipse." Ho scritto un cablaggio di prova per equals()e compareTo() github.com/GlenKPeterson/TestUtils Quasi tutte le implementazioni che ho mai testato mancavano. Come si utilizzano le raccolte se equals()e hashCode()non funzionano insieme in modo corretto ed efficiente? Faccio ancora il tifo per il resto della tua risposta e ho votato per votazione. Concedo anche che alcuni metodi eguali () generati automaticamente potrebbero non necessitare di test, ma ho avuto così tanti bug con implementazioni scadenti che mi rende nervoso.
GlenPeterson,

1
@GlenPeterson Accetto. Un mio collega ha scritto EqualsVerifier per questo scopo. Vedi github.com/jqno/equalsverifier
Tohnmeister il

@ Ӎσᶎ no, devi ancora testare input inaccettabili, è così che le persone trovano exploit di sicurezza.
gbjbaanb,

11

Non può essere generalizzato.

Se devo implementare una formula o un algoritmo dal rendering basato fisicamente, può benissimo passare 10 ore a test di unità paranoiche, poiché so che il minimo bug o imprecisione può portare a bug che sono quasi impossibili da diagnosticare, mesi dopo .

Se voglio solo raggruppare logicamente alcune righe di codice e dargli un nome, usato solo nell'ambito del file, potrei non provarlo affatto (se insisti a scrivere test per ogni singola funzione, senza eccezioni, i programmatori potrebbero ripiegare scrivere il minor numero di funzioni possibile).


Questo è un punto di vista davvero prezioso. La domanda ha bisogno di più contesto per avere una risposta completa.
CLF

3

Sì, è normale se stai parlando di TDDing. Quando si dispone di test automatici, si garantisce il comportamento desiderato del codice. Quando si scrivono prima i test, si determina se il codice esistente ha già il comportamento desiderato.

Ciò significa che:

  • Se si scrive un test che ha esito negativo, la correzione del codice con la cosa più semplice che funziona è più breve della scrittura del test.
  • Se si scrive un test che passa, non si ha più codice da scrivere, spendendo effettivamente più tempo per scrivere test che codice.

(Ciò non tiene conto del refactoring del codice, che mira a dedicare meno tempo alla scrittura del codice successivo. È bilanciato dal refactoring di test, che mira a dedicare meno tempo alla scrittura dei test successivi).

Sì, anche se stai parlando di scrivere test dopo il fatto, passerai più tempo:

  • Determinare il comportamento desiderato.
  • Determinare i modi per testare il comportamento desiderato.
  • Soddisfare le dipendenze del codice per poter scrivere i test.
  • Codice di correzione per test non riusciti.

Di quanto spendi effettivamente scrivendo codice.

Quindi sì, è una misura prevista.


3

Trovo che sia la parte più importante.

Il test unitario non si basa sempre sul "vedere se funziona bene", ma sull'apprendimento. Una volta testato qualcosa di abbastanza, questo diventa "hardcoded" nel tuo cervello e alla fine riduci il tempo di test dell'unità e puoi ritrovarti a scrivere intere classi e metodi senza testare nulla fino a quando non hai finito.

Questo è il motivo per cui una delle altre risposte in questa pagina menziona che in un "corso" hanno fatto il 90% di prove, perché tutti avevano bisogno di imparare le avvertenze del loro obiettivo.

Il test unitario non è solo un uso molto prezioso del tuo tempo in quanto migliora letteralmente le tue abilità, è un buon modo per ripassare il tuo codice e trovare un errore logico lungo la strada.


2

Può essere per molte persone, ma dipende.

Se stai scrivendo prima i test (TDD), potresti riscontrare delle sovrapposizioni nel tempo impiegato per scrivere il test, in realtà è utile per scrivere il codice. Tener conto di:

  • Determinazione di risultati e input (parametri)
  • Convenzioni di denominazione
  • Struttura: dove mettere le cose.
  • Semplicemente vecchio pensiero

Quando scrivi i test dopo aver scritto il codice, potresti scoprire che il tuo codice non è facilmente testabile, quindi scrivere i test è più difficile / richiede più tempo.

La maggior parte dei programmatori ha scritto codice molto più a lungo dei test, quindi mi aspetto che la maggior parte di essi non sia fluente. Inoltre puoi aggiungere il tempo necessario per comprendere e utilizzare il tuo framework di test.

Penso che dobbiamo cambiare la nostra mentalità su quanto tempo richiede il codice e su come è coinvolto il test unitario. Non guardarlo mai a breve termine e mai e poi mai solo confrontare il tempo totale per fornire una particolare funzione perché devi considerare non solo che stai scrivendo un codice migliore / meno buggy, ma un codice che è più facile da cambiare e lo rende ancora migliore / meno buggy.

Ad un certo punto, siamo tutti in grado di scrivere codice così buono, quindi alcuni degli strumenti e delle tecniche possono offrire così tanto solo nel migliorare le nostre capacità. Non è che potrei costruire una casa se solo avessi una sega laser guidata.


2

È normale dedicare più tempo, se non di più, alla scrittura dei test rispetto al codice reale?

Sì. Con alcuni avvertimenti.

Prima di tutto, è "normale" nel senso che la maggior parte dei grandi negozi funziona in questo modo, quindi anche se in questo modo era completamente fuorviata e stupida, il fatto che la maggior parte dei grandi negozi funzionasse in questo modo lo rende "normale".

Con questo non intendo implicare che testare sia sbagliato. Ho lavorato in ambienti senza test e in ambienti con test ossessivo-compulsivo e posso ancora dirti che anche il test ossessivo-compulsivo era meglio di nessun test.

E non faccio ancora TDD, (chissà, potrei in futuro), ma eseguo la maggior parte dei miei cicli di modifica-corsa-debug eseguendo i test, non l'applicazione reale, quindi, naturalmente, lavoro molto sui miei test, in modo da evitare il più possibile di dover eseguire l'applicazione effettiva.

Tuttavia, tenere presente che vi sono pericoli nei test eccessivi e in particolare nella quantità di tempo impiegata per il mantenimento dei test. (Scrivo principalmente questa risposta per evidenziarlo in modo specifico.)

Nella prefazione di The Art of Unit Testing (Manning, 2009) di Roy Osherove l'autore ammette di aver partecipato a un progetto che è fallito in gran parte a causa dell'enorme carico di sviluppo imposto da test unitari mal progettati che dovevano essere mantenuti per tutto il durata dello sforzo di sviluppo. Quindi, se ti ritrovi a passare troppo tempo a fare nulla ma a mantenere i tuoi test, questo non significa necessariamente che sei sulla strada giusta, perché è "normale". Il tuo sforzo di sviluppo potrebbe essere entrato in una modalità non salutare, in cui potrebbe essere necessario un ripensamento radicale della tua metodologia di test per salvare il progetto.


0

È normale dedicare più tempo, se non di più, alla scrittura dei test rispetto al codice reale?

  • per la scrittura di unit test (test di un modulo in isolamento) se il codice è altamente accoppiato (legacy, separazione dei problemi insufficiente , iniezione di dipendenza mancante , non sviluppata )
  • per la scrittura di test di integrazione / accettazione se la logica da testare è raggiungibile solo tramite gui-code
  • No per la scrittura di test di integrazione / accettazione purché il codice gui e la logica aziendale siano separati (il test non deve interagire con la gui)
  • No per la scrittura di unit test in caso di separazione delle preoccupazioni, iniezione di dipendenza, codice sviluppato da testdriven (tdd)
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.