Quanto devono essere granulari i test TDD?


18

Durante l'addestramento TDD basato su un caso di software medico stiamo implementando la seguente storia: "Quando l'utente preme il pulsante Salva, il sistema dovrebbe aggiungere paziente, aggiungere dispositivo e aggiungere record di dati del dispositivo".

L'implementazione finale sarà simile a questa:

if (_importDialog.Show() == ImportDialogResult.SaveButtonIsPressed)
{
   AddPatient();
   AddDevice();
   AddDeviceDataRecords();
}

Abbiamo due modi per implementarlo:

  1. Tre test in cui ciascuno verifica un metodo (AddPatient, AddDevice, AddDeviceDataRecords) è stato chiamato
  2. È stato chiamato un test che verifica tutti e tre i metodi

Nel primo caso se succede qualcosa di sbagliato se la condizione della clausola, tutti e tre i test falliranno. Ma nel secondo caso se il test fallisce, non siamo sicuri di cosa sia esattamente sbagliato. In che modo preferiresti.

Risposte:


8

Ma nel secondo caso se il test fallisce, non siamo sicuri di cosa sia esattamente sbagliato.

Penso che dipenderebbe in larga misura da quanto buoni messaggi di errore generano il test. In generale, ci sono diversi modi per verificare che sia stato chiamato un metodo; Ad esempio, se si utilizza un oggetto simulato, verrà visualizzato un messaggio di errore preciso che descrive quale metodo previsto non è stato chiamato durante il test. Se si verifica che il metodo è stato chiamato tramite il rilevamento degli effetti della chiamata, spetta a voi produrre un messaggio di errore descrittivo.

In pratica, la scelta tra le opzioni 1 e 2 dipende anche dalla situazione. Se vedo il codice che mostri sopra in un progetto legacy, scelgo l'approccio pragmatico del caso n. 2 solo per verificare che ciascuno dei 3 metodi sia chiamato correttamente quando la condizione è soddisfatta. Se sto sviluppando questo pezzo di codice in questo momento, le 3 chiamate al metodo verrebbero probabilmente aggiunte una ad una, in punti separati nel tempo (possibilmente a giorni o mesi di distanza l'una dall'altra), quindi aggiungerei un nuovo test unit separato per verificare ogni chiamata.

Si noti inoltre che in entrambi i casi, è necessario disporre di unit test separati per verificare che ciascuno dei singoli metodi faccia quello che dovrebbe fare.


Non trovi ragionevole combinare questi tre test in uno solo?
SiberianGuy,

@Idsa, può essere una decisione ragionevole, anche se in pratica raramente mi preoccupo di questo tipo di refactoring. Inoltre, sto lavorando con il codice legacy, in cui le priorità sono diverse: ci concentriamo sull'aumento della copertura dei test del codice esistente e sul mantenimento della quantità crescente di test unitari mantenibili.
Péter Török,

30

La granularità nel tuo esempio sembra essere la differenza tra test unitari e di collaudo.

Unittest verifica una singola unità di funzionalità, con il minor numero possibile di dipendenze. Nel tuo caso, potrebbero esserci 4 unittest

  • AddPatient aggiunge un paziente (ovvero chiama le funzioni del database pertinenti)?
  • AddDevice aggiunge un dispositivo?
  • AddDeviceDataRecords aggiunge i record?
  • fa la funzione principale unamend nella tua chiamata di esempio AddPatient, AddDevice e AddDeviceFunctions

Gli unittest sono per gli sviluppatori , quindi hanno fiducia nel fatto che il loro codice sia tecnicamente corretto

I test di accettazione dovrebbero testare la funzionalità combinata, dal punto di vista dell'utente. Dovrebbero essere modellati lungo le storie degli utenti ed essere il più alto livello possibile. Pertanto, non è necessario verificare se vengono chiamate funzioni, ma se si ottiene un vantaggio visibile all'utente :

quando l'utente immette i dati, fa clic su OK e ...

  • ... va all'elenco dei pazienti, dovrebbe vedere un nuovo paziente con il nome indicato
  • ... va all'elenco dei dispositivi, dovrebbe vedere un nuovo dispositivo
  • ... va ai dettagli del nuovo dispositivo, dovrebbe vedere nuovi datarecords

i test di accettazione sono per i clienti o per costruire una migliore comunicazione con loro.

Per rispondere alla tua domanda "cosa preferiresti": qual è il problema più grande per te in questo momento, bug e regressione (=> più unittest) o comprensione e formalizzazione del quadro generale (=> più test di accettazione)


13

Abbiamo due modi per implementarlo:

Questo è falso.

Tre test in cui ciascuno verifica un metodo (AddPatient, AddDevice, AddDeviceDataRecords) è stato chiamato

È necessario fare questo per essere sicuri che funziona.

È stato chiamato un test che verifica tutti e tre i metodi

È inoltre necessario farlo per essere sicuri che l'API funzioni.

La classe - come unità - deve essere completamente testata. Ogni metodo.

Puoi iniziare con un test che copre tutti e tre i metodi, ma non ti dice molto.

se il test fallisce, non siamo sicuri di cosa sia esattamente sbagliato.

Corretta. Ecco perché testerai tutti i metodi.

È necessario testare l'interfaccia pubblica. Dal momento che questa classe fa tre più uno (anche se sono raggruppati in un metodo a causa della storia dell'utente) è necessario testare tutte e quattro le cose. Tre di basso livello e un pacchetto.


2

Scriviamo i nostri test unitari per frasi significative di funzionalità che molte volte mappano su un metodo (se hai scritto bene il tuo codice), ma a volte diventano più grandi, comprendendo molti metodi.

Ad esempio, immagina che l'aggiunta di un paziente al tuo sistema abbia bisogno di alcune subroutine (funzioni figlio) da chiamare:

  1. VerifyPatientQualification
  2. EnsureDoctorExistence
  3. CheckInsuranceHistory
  4. EnsureEmptyBed

Potremmo anche scrivere unit test per ciascuna di queste funzioni.


2

Una semplice regola empirica che ho seguito è quella di nominare il test in modo che descriva esattamente cosa fa il test. Se il nome del test diventa troppo complesso, significa che forse il test sta facendo troppo. Quindi, ad esempio, nominare un test per fare ciò che proponi nell'opzione 2 può apparire come PatientIsAddedDeviceIsAddedAndDeviceDataRecordsWhenSaved che è molto più complesso di tre test separati PatientIsAddedWhenSaved, DeviceIsAddedWhenSaved, DataRecordsWhenSaved. Penso anche che le lezioni che possono essere apprese dal BDD siano piuttosto interessanti in cui ogni test è davvero rappresentativo di un singolo requisito che potrebbe essere descritto in un linguaggio naturale.

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.