2012-04-02 13 views
5

In un sacco di tutorial TDD vedo codice come questo:Unit Testing e di codificazione design

public class MyClass 
{ 
    public void DoSomething(string Data) 
    { 
     if (String.IsNullOrWhiteSpace(Data)) 
     throw new NullReferenceException(); 
    } 
} 


[Test] 
public DoSomething_NullParameter_ThrowsException() 
{ 
    var logic = MyClass(); 

    Assert.Throws<NullReferenceException>(() => logic.DoSomething(null)); 
} 

tutto questo ha un senso, ma ad un certo punto si sta per raggiungere la classe che utilizza effettivamente MyClass e si desidera per verificare che l'eccezione è gestita:

public class EntryPointClass 
{ 
    public void DoIt(string Data) 
    { 
    var logicClass = new MyClass(); 

    try 
    { 
     logicClass.DoSomething(Data); 
    } 
    catch(NullReferenceException ex) 
    { 

    } 
    } 
} 

[Test] 
public DoIt_NullParameter_IsHandled() 
{ 
    var logic = new EntryPointClass() 

    try 
    { 
    logic.DoIt(null); 
    } 
    catch 
    { 
    Assert.Fail(); 
    } 
} 

Quindi perché non mettere il try/catch in MyClass, in primo luogo, piuttosto che gettare l'eccezione e avere la prova che il test di per nulla nella classe di test di unità MyClass e non nella classe di test dell'unità EntryPointClass?

risposta

3

In genere, la vostra gestione delle eccezioni sarà simile a questa:.

public class EntryPointClass 
{ 
    Logger _logfile; 
    // ... 
    public void DoIt(string Data) 
    { 
    var logicClass = new MyClass(); 

    try 
    { 
     logicClass.DoSomething(Data); 
    } 
    catch(NullReferenceException ex) 
    { 
     _logfile.WriteLine("null reference exception occured in method 'DoIt'"); 
    } 
    } 
} 

(Il Logger è solo un esempio di una risorsa che serve per la gestione non è disponibile nel luogo in cui MyClass vita corretta eccezione Si potrebbe anche aggiungere la visualizzazione di una finestra di messaggio qui o qualcosa del genere.)

Al livello di MyClass, in genere non sono disponibili gli strumenti appropriati per la corretta gestione delle eccezioni (e non si desidera aggiungerli lì per mantenere la classe disaccoppiata, per esempio, da un mechani di registrazione specifico sm).

Si noti che questa decisione di progettazione è indipendente dal fare TDD. Se vuoi che la tua classe MyClass acquisisca effettivamente le eccezioni da sola, devi scrivere i test in modo diverso, giusto. Ma questa è solo una buona idea se l'individuazione delle eccezioni non blocchi i test automatici. Prova, ad esempio, a scrivere un buon test unitario per MyClass quando MyClass mostra da solo una finestra di avviso di avvertenza codificata.

Ovviamente, l'esempio sopra mostra che l'unità di test EntryPointClass può diventare più difficile nella realtà quando si ha bisogno di qualcosa come un logger per far funzionare le cose. In generale, è possibile attaccare questo problema fornendo il logger in fase di costruzione utilizzando un'interfaccia di ILogger che consente di sostituire il logger con una simulazione. O in questo caso semplice, può essere sufficiente per non inizializzare il logger per il test di unità a tutti e il codice in questo modo:

public class EntryPointClass 
{ 
     // .... 
     catch(NullReferenceException ex) 
     { 
      if(_logfile!=null) 
       _logfile.WriteLine("null reference exception occured in method 'DoIt'"); 
     }  
     // .... 
} 
+0

Perché non ho un evento in MyClass per gestire gli errori e quando si verifica un errore sollevare l'evento? –

+1

Jobs @Steve: questa è anche una possibile soluzione per mantenere le classi disaccoppiate, ma perché vuoi rendere le cose più complicate del necessario?Le eccezioni sono * il meccanismo standard * se gli errori non possono essere gestiti nel luogo in cui si verificano; costruire il proprio meccanismo di eventi è come reinventare la ruota. –

+0

Se ad esempio si dispone di un elenco e si effettua un controllo per vedere se è vuoto. Vuoi fare un'eccezione o faresti un evento amichevole? –

1

Eccezioni indicano situazione eccezionale per il codice (vedere le linee guida MSDN Exception Handling). Se l'argomento null è eccezionale per DoSomething, poi gettando ha perfettamente senso indipendentemente da ciò che e come userà quella classe successiva. La parte vale la pena evidenziare dalla sezione Exception Throwing che la descrive in modo preciso:

Segnala i problemi di esecuzione generando eccezioni. Se un membro non riesce a fare ciò che è stato progettato per farlo, dovrebbe essere considerato un errore di esecuzione e dovrebbe essere generata un'eccezione.

per non parlare, al momento della scrittura DoSomething si potrebbe non sapere quale codice lo userà (in realtà non si dovrebbe sapere evento, e l'assistenza).

Di conseguenza, se il lancio di eccezioni fa parte del contratto di codice, deve essere testato e deve essere testato come parte dei test DoSomething.

1

Perché non mettere il try/catch in MyClass, in primo luogo ...

Le eccezioni sono per disaccoppiare il rilevamento degli errori e la gestione degli errori. Se l'errore non può essere gestito (in tutti i casi) localmente in MyClass, allora dovresti generare un'eccezione.

Ciò consente un rilevamento di errore locale (MyClass) e una gestione degli errori da parte di chiunque rilevi l'eccezione.


Il test in EntryPointClass test, se il metodo DoIt è null-safe. Ha poco a che fare con il suo uso di MyClass.

In altre parole: il test in EntryPointClass non ha e (per la stabilità del codice e problemi di incapsulamento) non deve fare affidamento sul fatto che si utilizza MyClass.

1

unità test test unità di lavoro

Scrivere un test che mette alla prova viene generata un'eccezione quindi scrivere il codice per quella specifica e guardare il passaggio di prova (questo è il tuo primo esempio)

Poi scrivere il codice che si occupa dell'eccezione (se possibile) se lo fa, allora il test non dovrebbe tenere conto di tale eccezione man mano che l'hai gestita e dovresti misurare che l'output è atteso da quel metodo.

Se non si riesce a gestire l'eccezione, si deve omettere il passaggio catch (a meno che non si stia effettuando il log in questa fase) e si lasci andare sullo stack in questo caso si asserisce nuovamente che il metodo prevede un'eccezione.

Non si inserisce mai un codice di prova nel codice di test, si asserisce solo che un'eccezione è o non è stata lanciata.

Come testare la funzione corretta della classe.

+0

Sicuramente è necessario testare qualche parte che yo hai un blocco try/catch in modo che tu sappia che l'app non andrà in crash –

+0

Modificata in modo massiccio questa risposta perché non ero abbastanza chiaro spero che questo aiuti –

+0

Quindi stai dicendo stick e prova/catch in, ma in realtà non scrivi alcun test per vero? –

1

In realtà si sta testando due condizioni molto diverse:

  1. test che MyClass genera un'eccezione quando l'argomento è valido
  2. prova come EntryPointClass, come utente di MyClass, si occupa con l'eccezione generata da MyClass