Test delle unità di divisione per requisito o metodo


16

In primo luogo, mi scuso per il titolo, non riuscivo a pensare al modo più semplice per spiegarlo!

Ho un metodo per cui voglio scrivere unit test. Lo terrò abbastanza generico in quanto non voglio discutere l'implementazione del metodo, ma solo il suo test. Il metodo è:

public void HandleItem(item a)
{         
     CreateNewItem();
     UpdateStatusOnPreviousItem();
     SetNextRunDate();
}

Quindi questa classe ha un metodo pubblico che quindi chiama alcuni metodi privati ​​per eseguire la logica.

Quindi quando scrivo il test unitario voglio controllare che tutte e tre le cose siano state fatte. Dato che sono tutti chiamati nella stessa corsa, ho pensato che avrei potuto farlo come un test:

public void GivenItem_WhenRun_Thenxxxxx
{
     HandleItem(item);
     // Assert item has been created
     // Assert status has been set on the previous item
     // Assert run date has been set
}

Ma ho pensato di poterlo scrivere anche come tre test separati:

public void GivenItem_WhenRun_ThenItemIsCreated()
{
    HandleItem(item);
}

public void GivenItem_WhenRun_ThenStatusIsUpdatedOnPreviousItem()
{
   HandleItem(item);
}

public void GivenItem_WhenRun_ThenRunDateIsSet()
{
     HandleItem(item);
}

Quindi per me questo sembra più bello in quanto essenzialmente elenca i requisiti, ma poi tutti e tre sono correlati e richiedono esattamente lo stesso lavoro eseguito sul metodo testato, quindi sto eseguendo lo stesso codice 3 volte.

C'è un approccio raccomandato da adottare con questo?

Grazie

Risposte:


29

C'è una sottile differenza tra entrambi gli approcci. Nel primo caso, quando il primo Assertfallisce, gli altri due non vengono più eseguiti. Nel secondo caso, tutti e tre i test vengono sempre eseguiti, anche se uno fallisce. A seconda della natura della funzionalità testata, questo potrebbe adattarsi o non adattarsi bene al tuo caso:

  • se ha senso eseguire i tre asserzioni indipendentemente da un altro, perché quando uno fallisce, gli altri due potrebbero ancora non fallire, quindi il secondo approccio ha il vantaggio di ottenere i risultati completi dei test per tutti e 3 i test in una corsa. Questo può essere utile se si hanno tempi di compilazione notevoli, poiché ti dà la possibilità di correggere fino a 3 errori contemporaneamente prima di eseguire la build successiva.

  • se, tuttavia, un fallimento del primo test implicherà sempre che anche gli altri due test falliranno, allora è probabilmente meglio usare il primo approccio (poiché non ha molto senso eseguire un test se lo sai già in anticipo lo farà fallire).


2
+1, buon punto. Non mi è venuto in mente che i tempi di costruzione possano anche essere un collo di bottiglia.
Kilian Foth,

1
@KilianFoth: Non lavori abbastanza spesso in C ++ :(
Matthieu M.

1
@MatthieuM .: per essere onesti, la domanda è taggata "C #"
Doc Brown,

10

Risposta breve: è molto più importante che i test coprano tutte le funzionalità rispetto a come lo fanno.

Risposta più lunga: se vuoi ancora scegliere tra queste soluzioni in gran parte equivalenti, puoi utilizzare criteri ausiliari per ciò che è meglio. Per esempio,

  • leggibilità: se il metodo fa molte cose che non sono strettamente correlate, un test combinato potrebbe essere difficile da capire. Tuttavia, il metodo stesso potrebbe anche essere difficile da capire, quindi forse dovresti riformattare il metodo piuttosto che il test!)
  • efficienza: se l'esecuzione del metodo richiede molto tempo, questo potrebbe essere un motivo debole per combinare tutti e tre i controlli per risparmiare tempo
  • efficienza2: se l'esecuzione del codice di installazione del framework richiede molto tempo, ciò potrebbe anche essere un motivo debole per evitare più metodi di test. (Tuttavia, se questo è davvero un problema, probabilmente dovresti correggere o modificare la configurazione del test: i test di regressione perdono molto del loro valore se non riesci a eseguirli alla velocità della luce.)

2

Utilizzare una chiamata di metodo con più assert. Ecco perché:

Quando si verifica HandleItem (a), si sta verificando che il metodo ha riportato l'elemento nello stato corretto. Invece di "un assert per test", pensa "un concetto logico per test".

Domanda: se un CreateNewItem fallisce, ma gli altri due metodi hanno esito positivo, significa che HandleItem è stato completato correttamente? Immagino di no.

Con più asserzioni (con messaggi appropriati) saprai esattamente cosa non ha funzionato. In genere si verifica un metodo più volte per più input o stati di input, per evitare più asserzioni.

IMO, queste domande sono in genere un segno di qualcos'altro. È un segno che HandleItem non è davvero qualcosa che puoi "testare l'unità" dal momento che sembra delegare ad altri metodi. Quando stai semplicemente convalidando che HandleItem chiama correttamente altri metodi, diventa più un candidato al test di integrazione (nel qual caso avresti ancora 3 asserzioni).

Potresti prendere in considerazione l'idea di rendere pubblici gli altri 3 metodi e testarli in modo indipendente. O addirittura estraendoli in un'altra classe.


0

Usa il 2o approccio. Con il tuo primo approccio, se il test fallisce non capirai subito perché, perché potrebbe essere una delle 3 funzioni fallite. Con il secondo approccio saprai subito dove si è verificato il problema. È possibile inserire un codice duplicato all'interno della funzione Impostazione test.


-1

IMHO dovresti testare le tre parti di questo metodo separatamente in modo da sapere in modo più specifico dove stanno andando le cose quando lo fanno evitando di andare oltre la stessa parte del tuo codice due volte.


-2

Non credo ci sia un caso valido per scrivere metodi di prova separati per il tuo caso d'uso. Se si desidera ottenere i risultati da tutte e tre le condizioni variabili, è possibile testare tutti e tre e stamparne gli errori concatenandoli in a stringe affermando se la stringa è ancora vuota dopo aver completato il test. Mantenendoli tutti nello stesso metodo, le condizioni e i messaggi di errore documentano la post-condizione prevista del metodo in un unico punto, anziché dividerlo in tre metodi che potrebbero essere separati in un secondo momento.

Ciò significa che il tuo singolo test avrà più codice, ma se hai così tante post-condizioni che questo è un problema, probabilmente vorrai rifattorizzare i tuoi metodi di test per testare HandleItemcomunque i singoli metodi all'interno .

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.