2009-02-17 14 views

risposta

36

avere la copertura 100% del codice che non è grande come si può pensare. Consideriamo un esempio banale:

double Foo(double a, double b) 
{ 
    return a/b; 
} 

Anche un singolo test di unità alzerà copertura del codice di questo metodo al 100%, ma il suddetto test di unità non ci dirà quale codice funziona e cosa non è il codice. Questo potrebbe essere un codice perfettamente valido, ma senza testare le condizioni del bordo (ad esempio quando b è 0.0) il test dell'unità è inconcludente nella migliore delle ipotesi.

La copertura del codice indica solo ciò che è stato eseguito dai nostri test unitari, non se è stato eseguito correttamente. Questa è una distinzione importante da fare. Solo perché una riga di codice viene eseguita da un test unitario, non significa necessariamente che quella riga di codice funzioni come previsto.

Ascolta a this per una discussione interessante.

+1

Esempio banale molto bello. –

+0

+1 per il bellissimo esempio. –

+0

In quali piattaforme i doppi di divisione causano un comportamento "errato"? La maggior parte restituisce IEEE Infinity o NaN correttamente se vengono forniti gli input 0 o NaN e gli altri hanno meccanismi di eccezione che rappresentano solo un altro percorso di codice da coprire. –

11

La copertura del codice non significa che il codice sia privo di bug in alcun modo. È una stima di quanto bene i casi di test coprono il codice sorgente. La copertura del codice al 100% implicherebbe che ogni linea di codice è stata testata, ma certamente nessuno stato del tuo programma lo è. Ci sono ricerche in questo campo, penso che si chiami modello di stato finito, ma è davvero un modo bruto di cercare di esplorare ogni stato di un programma.

Un modo più elegante di fare la stessa cosa è qualcosa chiamato interpretazione astratta. MSR (Microsoft Research) ha rilasciato qualcosa chiamato CodeContracts basato sull'interpretazione astratta. Scopri anche Pex, loro hanno enfatizzato la cleaver metodi di test del comportamento di run-time dell'applicazione.

Potrei scrivere un test veramente buono che mi dia una buona copertura, ma non ci sono garanzie che quel test esplorerà tutti gli stati che il mio programma potrebbe avere. Questo è il problema di scrivere test davvero buoni, il che è difficile.

copertura del codice non implica buoni test

3

Non ci possono essere sempre le eccezioni di runtime: Memoria riempire, database o altre connessioni non essere chiuse ecc ...

8

Uh? Qualsiasi tipo di errore logico ordinario, immagino? Corruzione della memoria, sovraccarico del buffer, semplice codice errato, assegnazione-invece-di-test, la lista continua. La copertura è solo quella, ti fa sapere che tutti i percorsi di codice sono eseguiti, non che sono corretti.

1

Beh, se i test non prova la cosa che avviene nel codice coperto. Se si dispone di questo metodo che aggiunge un certo numero di proprietà, ad esempio:

public void AddTo(int i) 
{ 
NumberA += i; 
NumberB -= i; 
} 

Se il test controlla solo la proprietà NumberA, ma non NumberB, allora si avrà una copertura del 100%, il test viene superato, ma NumberB volontà contiene ancora un errore.

Conclusione: una prova di unità con il 100% non garantisce che il codice è esente da bug.

3

consideri il codice seguente:

int add(int a, int b) 
{ 
    return a + b; 
} 

Questo codice poteva non implementare alcune funzionalità necessaria (cioè non incontrare un esigenze degli utenti finali): "copertura 100%" non necessariamente testare/rilevare funzionalità che dovrebbe essere implementato ma quale no.

Questo codice potrebbe funzionare per alcuni ma non tutti gli intervalli di dati di input (ad esempio quando a e b sono entrambi molto grandi).

5

1. "spazio dati" problemi

vostro codice (male):

void f(int n, int increment) 
{ 
    while(n < 500) 
    { 
    cout << n; 
    n += increment; 
    } 
} 

Il test:

f(200,100); 

Bug in uso mondo reale:

f(200,0); 

Il mio punto: il test può coprire il 100% delle linee del codice ma non (in genere) copre tutto il possibile spazio di dati di input, ovvero l'insieme di tutti i possibili valori di input.

2. Test contro il proprio errore

Un altro esempio classico è quando si prende una decisione sbagliata nel design, e testare il codice contro la propria decisione sbagliata.

E.g. Il documento spec dice "stampare tutti i numeri primi fino a n" e si stampa tutti i numeri primi fino a n ma escluson. E i tuoi test unitari mettono alla prova la tua idea sbagliata.

3. comportamento indefinito

Utilizzare il valore di variabili non inizializzate, causare un accesso alla memoria non valida, ecc e il codice è indefinito comportamento (in C++ o in qualsiasi altra lingua che contempla "un comportamento indefinito"). A volte passeranno i test, ma si bloccherà nel mondo reale.

...

2

Errori nei test :)

3

copertura del codice non significa nulla, se le tue test contenere bug, o si sta testando la cosa sbagliata.

Come tangente correlata; Vorrei ricordare a voi che posso banalmente costruire il metodo (1) che soddisfa il seguente test pseudo-codice un O:

sorted = sort(2,1,6,4,3,1,6,2); 

for element in sorted { 
    if (is_defined(previousElement)) { 
    assert(element >= previousElement); 
    } 

    previousElement = element; 
} 

bonus karma a Jon Skeet, che ha sottolineato la scappatoia stavo pensando su

+0

Potrebbe restituire la lista vuota, o semplicemente * qualsiasi * elenco ordinato: non controlla che l'output sia correlato all'input. –

+0

Dannazione, pensavo che fosse rimasto un mistero anche per pochi minuti. Non sorprendentemente, Jon, hai trovato la scappatoia;) –

6

Come io non l'ho visto ancora citato, vorrei aggiungere questa discussione che la copertura del codice fa non dirvi quale parte del codice è priva di bug.

Ti dice solo quali parti del tuo codice sono garantite come non testate.

3

La copertura del codice di solito indica solo quanti dei rami all'interno di una funzione sono coperti. Di solito non riporta i vari percorsi che potrebbero essere presi tra le chiamate di funzione. Molti errori nei programmi si verificano perché il passaggio da un metodo a un altro è sbagliato, non perché i metodi stessi contengono errori. Tutti i bug di questo modulo potrebbero ancora esistere nella copertura del 100% del codice.

3

In un recente articolo del software IEEE "Due errori e software privo di errori: una confessione", Robert Glass sosteneva che nel "mondo reale" ci sono più errori causati da quella che lui chiama logica mancante o combinatoria (che puo ' essere protetto da strumenti di copertura del codice) piuttosto che da errori logici (che possono).

In altre parole, anche con una copertura del 100% del codice si corre il rischio di incontrare questo tipo di errori. E la cosa migliore che puoi fare è - hai indovinato - fare più revisioni del codice.

Il riferimento alla carta è here e ho trovato un sommario approssimativo here.

1

Convalida degli argomenti, ovvero. Assegni nulli. Se prendi input esterni e li passi in funzioni, ma non controlli mai se sono validi/nulli, allora puoi ottenere una copertura del 100%, ma otterrai comunque una NullReferenceException se in qualche modo passi null nella funzione perché è ciò che il tuo database dà tu.

anche, overflow aritmetico, come

int result = int.MAXVALUE + int.MAXVALUE; 

Code Coverage copre solo il codice esistente, non sarà in grado di indicare dove si dovrebbe aggiungere altro codice.

3

opere sulla mia macchina

Molte cose funzionano bene sulla macchina locale e non siamo in grado di assicurare che a lavorare su Staging/Produzione. La copertura del codice potrebbe non coprirlo.

1

Non conosco nessun altro, ma non raggiungiamo quasi il 100% di copertura. Nessuno dei nostri "Questo non dovrebbe mai accadere" CATCH vengono esercitati nei nostri test (beh, a volte lo fanno, ma poi il codice viene corretto in modo che non lo facciano più!). Purtroppo non mi preoccupo che possa esserci un errore di sintassi/logica in un mai accaduto-CATCH

1

Il prodotto potrebbe essere tecnicamente corretto, ma non soddisfare le esigenze del cliente.

1

Cordiali saluti, Microsoft Pex cerca di dare una mano, esplorando il codice e trovare casi "edge", come divisione per zero, troppo pieno, ecc

Questo strumento fa parte di VS2010, anche se è possibile installare un anteprima di tecnologia versione in VS2008. È piuttosto notevole che lo strumento trovi le cose che trova, però, IME, non ti porterà ancora fino a "antiproiettile".

0

Eseguire una copertura del codice 100%, vale a dire, 100 istruzioni%, domini di input e output di 100%, percorsi 100%, 100% qualunque cosa si pensi di, e ancora possono avere bug nel codice: mancanti caratteristiche.

0

La copertura del codice non significa molto. Ciò che importa è se tutti (o la maggior parte) dei valori degli argomenti che influenzano il comportamento sono coperti.

Per esempio, si consideri un metodo tipico compareTo (in Java, ma si applica nella maggior parte delle lingue):

//Return Negative, 0 or positive depending on x is <, = or > y 
int compareTo(int x, int y) { 
    return x-y; 
} 

Finché si dispone di un test per compareTo(0,0), si ottiene la copertura del codice. Comunque, hai bisogno di almeno 3 testicoli qui (per i 3 risultati). Ancora non è privo di bug. Vale anche la pena aggiungere test per coprire condizioni eccezionali/di errore. Nel caso precedente, se provi compareTo(10, Integer.MAX_INT), fallirà.

Bottomline: tenta di suddividere l'input in disgiunti insiemi in base al comportamento, eseguire un test per almeno un input da ciascun set. Questo aggiungerà più copertura nel vero senso.

Controllare anche gli strumenti come QuickCheck (se disponibile per la lingua).

0

Come indicato in molte delle risposte qui, potresti avere una copertura del 100% del codice e avere ancora bug.

Oltre a ciò, è possibile avere 0 bug ma la logica nel codice potrebbe non essere corretta (non corrisponde ai requisiti). La copertura del codice, o essere al 100% privo di bug, non può aiutarti affatto.

Una tipica pratica di sviluppo di software aziendale potrebbe essere la seguente:

  1. Avere una specifica funzionale chiaramente scritto
  2. Avere un piano di test che è scritto contro la (1) e lo hanno peer reviewed
  3. Avere prova casi scritti contro (2) e averli sottoposti a peer review
  4. Scrivere il codice in base alle specifiche funzionali e farlo revisionare peer
  5. Testare il codice contro i tes t casi
  6. Effettuare un'analisi della copertura del codice e scrivere più casi di test per ottenere una buona copertura.

Nota che ho detto "buono" e non "100%". Una copertura del 100% potrebbe non essere sempre fattibile per raggiungere - nel qual caso la tua energia è meglio spesa per raggiungere la correttezza del codice, piuttosto che la copertura di alcuni rami oscuri. Diversi tipi di cose possono andare storto in uno dei passaggi da 1 a 5 sopra: idea sbagliata, specifica sbagliata, test errati, codice errato, esecuzione errata del test ... La linea di fondo è che il passaggio 6 da solo non è il passo più importante in il processo.

esempio concreto di codice errato che non ha bug e ha 100% di copertura:

/** 
* Returns the duration in milliseconds 
*/ 
int getDuration() { 
    return end - start; 
} 

// test: 

start = 0; 
end = 1; 
assertEquals(1, getDuration()); // yay! 

// but the requirement was: 
// The interface should have a method for returning the duration in *seconds*. 
0

Quasi tutto.

Hai letto Code Complete? (Perché StackOverflow dice che sei veramente should.) Nel Capitolo 22 si dice "la copertura delle dichiarazioni al 100% è un buon inizio, ma è appena sufficiente". Il resto del capitolo spiega come determinare quali test aggiuntivi aggiungere. Ecco un breve assaggio.

  • test base Structured e dati di valutazione del flusso significa testare ogni percorso logico attraverso il programma. Esistono quattro percorsi attraverso il codice inventato sottostante, in base ai valori di A e B. È possibile ottenere una copertura di istruzioni al 100% testando solo due dei quattro percorsi, forse f=1:g=1/f e f=0:g=f+1. Ma darà un errore di divisione per zero. Dovete considerare se dichiarazioni e mentre e per loop (il corpo del ciclo potrebbe mai essere eseguito) e ogni ramo di un selezionano o interruttore dichiarazione.

    If A Then
    f = 1
    Else
    f = 0
    End If
    If B Then
    g = f + 1
    Else
    g = f/0
    End If

  • errore indovinare - informato congetture circa i tipi di input che sono spesso causa di errori. Ad esempio condizioni al contorno (disattivate da un errore), dati non validi, valori molto grandi, valori molto piccoli, zeri, valori nulli, raccolte vuote.

E anche così non ci può essere degli errori di vostre esigenze, errori in alcuni test, ecc - come altri hanno detto.

Problemi correlati