Unità che verifica più condizioni in un'istruzione IF


26

Ho un pezzo di codice che assomiglia a questo:

function bool PassesBusinessRules()
{
    bool meetsBusinessRules = false;

    if (PassesBusinessRule1 
         && PassesBusinessRule2
         && PassesBusinessRule3)
    {
         meetsBusinessRules= true;
    }

    return meetsBusinessRules;
}

Credo che dovrebbero esserci quattro test unitari per questa particolare funzione. Tre per testare ciascuna delle condizioni nell'istruzione if e assicurarsi che restituisca false. E un altro test che assicura che la funzione ritorni vera.

Domanda: in realtà dovrebbero esserci invece dieci test unitari? Nove che controlla ciascuno dei possibili percorsi di errore. IE:

  • Falso Falso Falso
  • Falso Falso Vero
  • Falso Vero Falso

E così via per ogni possibile combinazione.

Penso che sia eccessivo, ma alcuni degli altri membri del mio team no. Il modo in cui lo guardo è se BusinessRule1 fallisce, quindi dovrebbe sempre restituire false, non importa se è stato controllato per primo o per ultimo.


Il compilatore utilizza una valutazione avida per l'operatore &&?
Suszterpatt,

12
Se hai scritto 10 test unitari, testeresti l'operatore &&, non i tuoi metodi.
Mert Akcakaya,

2
Non ci sarebbero solo otto test se testassi tutte le possibili combinazioni? Tre parametri booleani attivati ​​o disattivati.
Kris Harper,

3
@Mert: solo se puoi garantire che && sarà sempre lì.
Misko,

Hickey: Se lo passiamo a scrivere test, è tempo che non stiamo spendendo per fare qualcos'altro. Ognuno di noi deve valutare il modo migliore di trascorrere il proprio tempo al fine di massimizzare i risultati, sia in termini di quantità che di qualità. Se le persone pensano che passare il cinquanta percento del loro tempo a scrivere test massimizzi i loro risultati, va bene per loro. Sono sicuro che non è vero per me — Preferirei passare quel tempo a pensare al mio problema. Sono certo che, per me, questo produce soluzioni migliori, con meno difetti, rispetto a qualsiasi altro uso del mio tempo. Un cattivo design con una suite di test completa è ancora un cattivo design.
Giobbe

Risposte:


29

Formalmente, questi tipi di copertura hanno nomi.

Innanzitutto, c'è una copertura predicata : vuoi avere un caso di test che renda vera l'istruzione if e una che la renda falsa. Avere questa copertura soddisfatta è probabilmente un requisito fondamentale per una buona suite di test.

Poi c'è la copertura delle condizioni : qui vuoi testare che ogni sotto-condizione nel if ha il valore vero e falso. Questo ovviamente crea più test, ma di solito cattura più bug, quindi è spesso una buona idea includere nella suite di test se hai tempo.

I criteri di copertura più avanzati sono generalmente chiamati Copertura combinatoria delle condizioni : qui l'obiettivo è avere un caso di test che attraversi tutte le possibili combinazioni di valori booleani nel test.

È meglio della semplice copertura di predicati o condizioni? In termini di copertura, ovviamente. Ma non è gratuito. La manutenzione dei test ha un costo molto elevato. Per questo motivo, la maggior parte delle persone non si preoccupa della copertura combinatoria completa. Di solito testare tutti i rami (o tutte le condizioni), sarà abbastanza buono per catturare i bug. L'aggiunta di test extra per i test combinatori di solito non catturerà più bug, ma richiede un grande sforzo per creare e mantenere. Lo sforzo extra di solito rende questo non degno del guadagno molto piccolo, quindi non lo consiglierei.

Parte di questa decisione dovrebbe essere basata su quanto rischioso pensi che il codice sarà. Se ha molto spazio per fallire, vale la pena testarlo. Se è in qualche modo stabile e non cambierà molto, dovresti considerare di concentrare i tuoi sforzi di test altrove.


2
Se i valori booleani vengono passati da fonti esterne (nel senso che non sono sempre convalidati), è spesso necessaria una copertura condizionale combinatoria. Prima fai una tabella delle combinazioni. Quindi, per ciascuna voce, decidere se quella voce rappresenta un caso d'uso significativo. In caso contrario, dovrebbe essere presente un codice da qualche parte (asserzioni software o clausole di convalida) per impedire l'esecuzione di tale combinazione. È importante non raggruppare tutti i parametri in un singolo test combinatorio: provare a suddividere i parametri in gruppi che interagiscono tra loro, ovvero condividono la stessa espressione booleana.
rwong

Quanto sei sicuro dei termini in grassetto? La tua risposta sembra essere l'unica occorrenza di "Copertura combinata delle condizioni" e alcune risorse affermano che "copertura predicata" e "copertura condizionale" sono la stessa cosa.
Stijn,

8

In definitiva, dipende da te (r team), dal codice e dall'ambiente di progetto specifico. Non esiste una regola universale. Tu (r team) dovresti scrivere tutti i test di cui hai bisogno per sentirti a tuo agio che il codice è effettivamente corretto . Quindi, se i tuoi compagni di squadra non sono convinti da 4 test, forse hai bisogno di più.

Il tempo OTOH di scrivere test unitari è di solito una risorsa scarsa. Quindi cerca di trovare il modo migliore per trascorrere il tempo limitato che hai . Ad esempio, se hai un altro metodo importante con una copertura dello 0%, potrebbe essere meglio scrivere un paio di test unitari per coprire quello, piuttosto che aggiungere ulteriori test per questo metodo. Naturalmente, dipende anche da quanto fragile sia l'implementazione di ciascuno. La pianificazione di molte modifiche a questo particolare metodo nel prossimo futuro può giustificare una copertura aggiuntiva per i test unitari. Quindi potrebbe essere su un percorso critico all'interno del programma. Questi sono tutti fattori che solo tu (il team) può valutare.

Personalmente sarei di solito felice con i 4 test che descrivi, cioè:

  • vero falso falso
  • falso vero falso
  • falso falso vero
  • vero vero vero

più forse uno:

  • vero vero falso

per garantire che l'unico modo per ottenere un valore di ritorno truesia quello di soddisfare tutte e 3 le regole aziendali. Ma alla fine, se i tuoi compagni di squadra insistono per avere percorsi combinatori coperti, potrebbe essere più economico aggiungere quei test extra che continuare l'argomento molto più a lungo :-)


3

Se vuoi essere sicuro, avresti bisogno di otto unit test usando le condizioni rappresentate da una tabella di verità a tre variabili ( http://teach.valdosta.edu/plmoch/MATH4161/Spring%202004/and_or_if_files/image006.gif ).

Non si può mai essere sicuri che la logica aziendale stabilirà sempre che i controlli vengano eseguiti in quell'ordine e si desidera che il test sappia il meno possibile sull'attuazione effettiva possibile.


2
Il test unitario è un test in white box.
Péter Török,

Bene, l'ordine non dovrebbe importare, && è comunista, o almeno dovrebbe essere
Zachary K,

2

Sì, dovrebbe esserci la combinazione completa in un mondo ideale.

Quando esegui il test unitario, dovresti davvero provare a ignorare come funziona il metodo. Basta fornire i 3 ingressi e verificare che l'uscita sia corretta.


1
Il test unitario è un test in white box. E non viviamo in un mondo ideale.
Péter Török,

@ PéterTörök - Non viviamo in un mondo ideale per essere sicuri, ma stackexchange non è d' accordo con te sull'altro punto. Soprattutto per TDD, i test sono scritti sulle specifiche, non sull'implementazione. Personalmente prendo la "specifica" per includere tutti gli input (comprese le variabili membro) e tutti gli output (inclusi gli effetti collaterali).
Telastyn,

1
È solo un thread specifico su StackOverflow, relativo a un caso specifico, che non dovrebbe essere sovra-generalizzato. Soprattutto perché questo post attuale riguarda ovviamente il test del codice che è già stato scritto.
Péter Török,

1

Lo stato è malvagio. La seguente funzione non ha bisogno di un test unitario perché non ha effetti collaterali ed è ben compreso cosa fa e cosa non fa. Perché provarlo? Non ti fidi del tuo cervello ??? Le funzioni statiche sono fantastiche!

static function bool Foo(bool a, bool b, bool c)
{
    return a && b && c;
}

2
No, non mi fido del mio cervello - ho imparato il modo difficile di ricontrollare sempre quello che faccio :-) Quindi avrei ancora bisogno di unit test per assicurarmi di non aver sbagliato a scrivere nulla e che nessuno stia andando per rompere il codice in futuro. E più unit test per verificare il metodo del chiamante che calcola lo stato rappresentato da a, be c. Puoi spostare la logica di business nel modo che preferisci, alla fine dovrai comunque testarla da qualche parte.
Péter Török,

@Péter Török, puoi anche fare errori di battitura nei tuoi test e quindi finire con falsi positivi, quindi dove ti fermi? Scrivi test unitari per i test unitari? Neanche io mi fido del mio cervello al 100%, ma alla fine scrivere codice è quello che faccio per vivere. È possibile avere un bug all'interno di questa funzione, ma è importante scrivere il codice in modo tale che un bug sia facile da rintracciare alla fonte e in modo che una volta isolato il problema e fatto una correzione, si sta meglio . Un codice ben scritto può fare affidamento su test di integrazione principalmente infoq.com/presentations/Simple-Made-Easy
Lavoro

2
In effetti anche i test possono essere difettosi. (TDD risolve questo problema facendo fallire prima i test.) Tuttavia, fare lo stesso tipo di errore due volte (e trascurarlo) ha una probabilità molto più bassa. In generale, nessuna quantità e tipo di test può dimostrare che il software è privo di bug, basta ridurre la probabilità di bug a un livello accettabile. E nella velocità di rintracciare i bug alla fonte, nulla di IMO può battere i test unitari - feedback rapido regolaz :-)
Péter Török

"La seguente funzione non richiede un test unitario" Penso che tu sia sarcastico qui, ma non è chiaro. Mi fido del mio cervello? NO! Mi fido del cervello del prossimo che tocca il codice? ANCORA PIÙ NO! Confido che tutte le ipotesi alla base del codice saranno vere tra un anno? ... hai la mia deriva. Inoltre, le funzioni statiche uccidono OO ... se si desidera eseguire FP, utilizzare un linguaggio FP.
Rob,

1

So che questa domanda è piuttosto vecchia. Ma voglio dare un'altra prospettiva al problema.

Innanzitutto, i test unitari dovrebbero avere due scopi:

  1. Crea documentazione per te e i tuoi compagni di squadra, così dopo un determinato periodo di tempo potresti leggere il test unitario e assicurarti di capire what's the class' intentionehow the class is doing its work
  2. Durante lo sviluppo, il test unitario si assicura che il codice che stiamo scrivendo stia facendo il suo lavoro come previsto nella nostra mente.

Quindi, ricapitolando il problema, vogliamo testare un complex if statement, per il dato esempio, ci sono 2 ^ 3 possibilità, che è una quantità importante di test che possiamo scrivere.

  • È possibile adattarsi a questo fatto e scrivere 8 test o utilizzare test parametrizzati
  • Puoi anche seguire le altre risposte e ricordare che i test dovrebbero essere chiari con l'intenzione, in questo modo non avremo problemi con troppi dettagli che nel prossimo futuro potrebbero rendere più difficile la comprensione what is doing the code

D'altra parte, se sei nella posizione in cui i tuoi test sono ancora più complessi dell'implementazione, è perché l'implementazione dovrebbe essere riprogettata (più o meno a seconda del caso) piuttosto che il test stesso.

Per le complesse istruzioni if, ad esempio, potresti pensare al modello di responsabilità della catena , implementando ciascun gestore in questo modo:

If some simple business rule apply, derive to the next handler

Quanto sarebbe semplice testare alcune semplici regole, anziché una complessa?

Spero che sia d'aiuto,


0

Questo è uno di quei casi in cui qualcosa come Quickcheck ( http://en.wikipedia.org/wiki/QuickCheck ) sarà tuo amico. Invece di scrivere tutti gli N casi a mano, il computer genera tutti (o almeno un gran numero) di possibili casi di test e convalida che tutti restituiscano un risultato ragionevole.

Programmiamo i computer per vivere qui, perché non programmare il computer per generare i casi di test per te?


0

È possibile riformattare le condizioni in condizioni di guardia:

if (! PassesBusinessRule1) {
    return false;
}

if (! PassesBusinessRule2) {
    return false;
}

if (! PassesBusinessRule3) {
    return false;
}

Non penso che ciò riduca il numero di casi, ma la mia esperienza è che è più facile risolverli in questo modo.

(Nota che sono un grande fan del "singolo punto di uscita", ma faccio un'eccezione per le condizioni di guardia. Ma ci sono altri modi per strutturare il codice in modo da non avere rendimenti separati.)

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.