Procedura dettagliata: creazione ed esecuzione di unit test per codice gestito
In questa procedura dettagliata viene illustrato come creare, eseguire e personalizzare una serie di unit test usando il framework di unit test di Microsoft per il codice gestito e Esplora test di Visual Studio.Sarà innanzitutto illustrato il progetto C# in fase di sviluppo, quindi saranno creati i test in cui sarà usato il codice e saranno esaminati i risultati.Sarà infine possibile modificare il codice del progetto ed eseguire nuovamente i test.
Questo argomento contiene le seguenti sezioni:
Fix your code and rerun your tests
Use unit tests to improve your code
[!NOTA]
Questa procedura dettagliata usa il framework di unit test di Microsoft per il codice gestito.Esplora test consente inoltre di eseguire test da framework di unit test di terze parti provvisti di adapter per Esplora test.Per altre informazioni, vedere Procedura: installare framework unit test di terze parti.
[!NOTA]
Per informazioni sulle modalità di esecuzione dei test dalla riga di comando, vedere Procedura dettagliata: Uso dell'utilità di test della riga di comando.
Prerequisiti
- Il progetto Bank.Vedere Progetto di esempio per la creazione di unit test.
Preparare la procedura dettagliata
Aprire Visual Studio.
Scegliere Nuovo dal menu File, quindi scegliere Progetto.
Verrà visualizzata la finestra di dialogo Nuovo progetto.
In Modelli installati fare clic su Visual C#.
Nell'elenco di tipi di applicazione fare clic su Libreria di classi.
Digitare Bank nella casella Nome, quindi scegliere OK.
[!NOTA]
Se il nome "Bank" è stato già usato, scegliere un altro nome per il progetto.
Viene creato nuovo progetto Bank, visualizzato in Esplora soluzioni con il file Class1.cs aperto nell'Editor di codice.
[!NOTA]
Per aprire il file Class1.cs nell'Editor di codice, nel caso non fosse aperto, fare doppio clic sul file stesso in Esplora soluzioni.
Copiare il codice sorgente da Progetto di esempio per la creazione di unit test.
Sostituire il contenuto originale di Class1.cs con il codice di Progetto di esempio per la creazione di unit test.
Salvare il file come BankAccount.cs
Scegliere Compila soluzione dal menu Compila.
A questo punto si dispone di un progetto denominato Bankche contiene il codice sorgente da testare e gli strumenti da usare a questo scopo.Nello spazio dei nomi per Bank, BankAccountNS, è presente la classe pubblica BankAccount, i cui metodi saranno testati nelle procedure seguenti.
In questa guida introduttiva viene illustrato soprattutto il metodo Debit.Il metodo Debit viene chiamato quando si preleva denaro da un conto e contiene il seguente codice:
// method under test
public void Debit(double amount)
{
if(amount > m_balance)
{
throw new ArgumentOutOfRangeException("amount");
}
if (amount < 0)
{
throw new ArgumentOutOfRangeException("amount");
}
m_balance += amount;
}
Creare un progetto di unit test
Prerequisito: attenersi alla procedura Prepare the walkthrough.
Per creare un progetto di unit test
Scegliere Aggiungi dal menu File, quindi Nuovo progetto.
Nella finestra di dialogo Nuovo progetto espandere Installato, Visual C#, quindi scegliere Test.
Nell'elenco dei modelli selezionare Progetto unit test.
Nella casella Nome immettere BankTest, quindi scegliere OK.
Il progetto BankTests verrà aggiunto alla soluzione Bank.
Nel progetto BankTests aggiungere un riferimento alla soluzione Bank.
In Esplora soluzioni selezionare Riferimenti nel progetto BankTests, quindi scegliere Aggiungi riferimento dal menu di scelta rapida.
Nella finestra di dialogo Gestione riferimenti espandere Soluzione, quindi selezionare l'elemento Bank.
Creare la classe di test
È necessaria una classe di test per verificare la classe BankAccount.È possibile usare UnitTest1.cs generato dal modello del progetto, ma è necessario fornire nomi più descrittivi per il file e la classe.È possibile eseguire questa operazione in un passaggio rinominando il file in Esplora soluzioni.
Ridenominazione di un file di classe
In Esplora soluzioni selezionare il file UnitTest1.cs nel progetto BankTests.Dal menu di scelta rapida scegliere Rinomina, quindi rinominare il file assegnandogli il nome BankAccountTests.cs.Scegliere Sì nella finestra di dialogo in cui viene chiesto se rinominare tutti i riferimenti nel progetto dell'elemento di codice 'UnitTest1'.Questo passaggio cambia il nome della classe in BankAccountTests.
Il file BankAccountTests.cs contiene ora il codice seguente:
// unit test code
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace BankTests
{
[TestClass]
public class BankAccountTests
{
[TestMethod]
public void TestMethod1()
{
}
}
}
Aggiungere un'istruzione using al progetto sottoposto a test
È inoltre possibile aggiungere un'istruzione using alla classe per consentire di effettuare chiamate nel progetto sottoposto a test senza usare nomi completi.All'inizio del file di classe, aggiungere:
using BankAccountNS;
Requisiti della classe di test
I requisiti minimi per una classe di test sono i seguenti:
L'attributo [TestClass] è obbligatorio nel framework per unit test di Microsoft per il codice gestito per qualsiasi classe che contiene metodi di unit test che si desidera eseguire in Esplora test.
Ogni metodo di test che si desidera eseguire in Esplora test deve avere l'attributo [TestMethod].
È possibile avere altre classi in un progetto di unit test che non presentano l'attributo [TestClass] ed è possibile avere altri metodi nelle classi di test non dispongono dell'attributo [TestMethod].È possibile usare questi altri metodi e classi nei metodi di test.
Creare il primo metodo di test
In questa procedura si scriveranno i metodi di unit test per verificare il comportamento del metodo Debit della classe BankAccount.Il metodo è elencato sopra.
Analizzando il metodo sottoposto a test, si determina che esistono almeno tre comportamenti che devono essere controllati:
Il metodo genera un oggetto [ArgumentOutOfRangeException] se la quantità del debito è superiore al saldo.
Genera inoltre ArgumentOutOfRangeException se la quantità del debito è minore di zero.
Se i controlli in 1.) e in 2.) vengono superati, il metodo sottrae l'importo dal saldo del conto.
Nel primo test usare un importo valido (uno che sia minore del saldo del conto e che sia maggiore di zero) per prelevare l'importo corretto dal conto.
Per creare un metodo di test
Aggiungere un'istruzione using BankAccountNS; al file BankAccountTests.cs.
Aggiungere il seguente metodo alla classe BankAccountTests:
// unit test code [TestMethod] public void Debit_WithValidAmount_UpdatesBalance() { // arrange double beginningBalance = 11.99; double debitAmount = 4.55; double expected = 7.44; BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance); // act account.Debit(debitAmount); // assert double actual = account.Balance; Assert.AreEqual(expected, actual, 0.001, "Account not debited correctly"); }
Il metodo è piuttosto semplice.Verrà configurato un nuovo oggetto BankAccount con un saldo iniziale e quindi verrà prelevato un importo valido.Verrà usato il framework di unit test di Microsoft per il codice gestito dal metodo AreEqual per verificare che il saldo finale sia quello previsto.
Requisiti del metodo di test
Un metodo di test deve soddisfare i seguenti requisiti:
Il metodo deve essere provvisto dell'attributo [TestMethod].
Il metodo deve restituire void.
Il metodo non può avere parametri.
Compilare ed eseguire il test
Per compilare ed eseguire il test
Scegliere Compila soluzione dal menu Compila.
Se non sono presenti errori, la finestra di UnitTestExplorer viene visualizzata con Debit_WithValidAmount_UpdatesBalance visualizzato nel gruppo Test non eseguiti.Se Esplora test non viene visualizzato al termine di una compilazione corretta, scegliere Test dal menu, quindi scegliere Windows e infine Esplora test.
Scegliere Esegui tutto per eseguire il test.Mentre il test è in esecuzione, la barra di stato nella parte superiore della finestra viene animata.Al termine del test, la barra diventa verde se tutti i metodi di test vengono superati oppure rossa se almeno uno dei test ha esito negativo.
In questo caso, il test non riesce.Il metodo di test viene spostato nel gruppo Test non superati.gruppo.Selezionare il metodo in Esplora test per visualizzare i dettagli nella parte inferiore della finestra.
Correggere il codice e rieseguire i test
Analizzare i risultati dei test
Il risultato del test contiene un messaggio che descrive l'errore.Per il metodo AreEquals, il messaggio mostra il contenuto previsto (il parametro (<XXX>previsto) e ciò che è stato ricevuto <YYY>effettivo).Si prevedeva che il saldo venisse detratto dal saldo iniziale, ma è stato invece aumentato dell'importo del prelievo.
Il riesame del codice di Debit mostra che lo unit test ha trovato un bug.La quantità da ritirare viene aggiunta al saldo del conto quando dovrebbe essere sottratta.
Correggere il bug
Per correggere l'errore, sostituire semplicemente la riga
m_balance += amount;
with
m_balance -= amount;
Eseguire nuovamente il test
In Esplora test scegliere Esegui tutto per rieseguire il test.La barra verde/rossa diventa verde e il test viene spostato nel gruppo Test superati.
Usare gli unit test per migliorare il codice
In questa sezione viene descritto come un processo iterativo di analisi, di sviluppo di unit test e di refactoring può aiutare a rendere il codice di produzione più affidabile ed efficace.
Analizzare i problemi
Dopo aver creato un metodo di test per confermare che un importo valido è stato correttamente detratto nel metodo Debit, è possibile tornare ai casi rimanenti nell'analisi originale:
Il metodo genera un oggetto ArgumentOutOfRangeException se la quantità del debito è superiore al saldo.
Genera inoltre ArgumentOutOfRangeException se la quantità del debito è minore di zero.
Creare i metodi di test
Un primo tentativo di creare un metodo di test per risolvere questi problemi sembra promettente:
//unit test method
[TestMethod]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public void Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange()
{
// arrange
double beginningBalance = 11.99;
double debitAmount = -100.00;
BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);
// act
account.Debit(debitAmount);
// assert is handled by ExpectedException
}
Verrà usato l'attributo ExpectedExceptionAttribute per confermare che è stata generata l'eccezione appropriata.L'attributo causa l'esito negativo del test a meno che non venga generata un'eccezione ArgumentOutOfRangeException.L'esecuzione del test con valori sia positivi che negativi di debitAmount e la modifica temporanea del metodo sottoposto a test per generare una ApplicationException generica quando la quantità è inferiore a zero indicano che il test funziona correttamente.Per eseguire il test del caso in cui l'importo prelevato è maggiore del saldo, è sufficiente eseguire:
Creare un nuovo metodo di test denominato Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange.
Copiare il corpo del metodo da Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange nel nuovo metodo.
Impostare debitAmount su un numero maggiore del saldo.
Eseguire i test
L'esecuzione di due metodi con valori diversi per debitAmount dimostra che i test gestiscono in modo adeguato i casi rimanenti.L'esecuzione di tutti e tre i test conferma che tutti i casi nell'analisi originale sono correttamente coperti.
Continuare l'analisi
Tuttavia, gli ultimi due metodi di test presentano alcuni problemi.Non è possibile essere certi di quale condizione nel codice sottoposto a test generi l'eccezione quando entrambi i test sono in esecuzione.Potrebbero essere utili alcuni modi per differenziare le due condizioni.Più si pensa al problema, più diventa evidente che conoscere quale condizione è stata violata aumenta la fiducia nei test.Queste informazioni sono inoltre utili al meccanismo di produzione che gestisce l'eccezione quando viene generata dal metodo sottoposto al test.La creazione di maggiori informazioni quando il metodo genera eccezioni sarebbe di aiuto, ma l'attributo ExpectedException non può generarle.
Analizzando di nuovo il metodo sottoposto a test, si nota che entrambe le istruzioni condizionali usano un costruttore ArgumentOutOfRangeException che assume il nome dell'argomento come parametro:
throw new ArgumentOutOfRangeException("amount");
Da una ricerca della MSDN Library si scopre che esiste un costruttore che fornisce informazioni molto più dettagliate.#ctor(String, Object, String) include il nome dell'argomento, il valore dell'argomento e un messaggio definito dall'utente.È possibile effettuare il refactoring del metodo sottoposto al test per usare questo costruttore.Ancor meglio, è possibile usare membri di tipo pubblico per specificare gli errori.
Effettuare il refactoring del codice sottoposto a test
Innanzitutto è necessario definire due costanti per i messaggi di errore nell'ambito di classe:
// class under test
public const string DebitAmountExceedsBalanceMessage = "Debit amount exceeds balance";
public const string DebitAmountLessThanZeroMessage = "Debit amount less than zero";
Quindi si modificano le due istruzioni condizionali nel metodo Debit:
// method under test
// ...
if (amount > m_balance)
{
throw new ArgumentOutOfRangeException("amount", amount, DebitAmountExceedsBalanceMessage);
}
if (amount < 0)
{
throw new ArgumentOutOfRangeException("amount", amount, DebitAmountLessThanZeroMessage);
}
// ...
Effettuare il refactoring dei metodi di test
Nel metodo di test prima si rimuove l'attributo ExpectedException.Al suo posto, si rileva l'eccezione generata e si verifica che sia stata generata nell'istruzione della condizione corretta.Tuttavia, è ora necessario scegliere tra due opzioni per verificare le condizioni rimanenti.Ad esempio nel metodo Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange, è possibile effettuare una delle seguenti azioni:
Confermare che la proprietà ActualValue dell'eccezione (il secondo parametro del costruttore ArgumentOutOfRangeException) è maggiore del saldo iniziale.Questa opzione richiede il test della proprietà ActualValue dell'eccezione con la variabile beginningBalance del metodo di test e richiede quindi di verificare che ActualValue sia maggiore di zero.
Confermare che il messaggio (il terzo parametro del costruttore) include DebitAmountExceedsBalanceMessage definito nella classe BankAccount.
Il metodo StringAssert.Contains nel framework per unit test di Microsoft consente di verificare la seconda opzione senza i calcoli richiesti per la prima opzione.
Un secondo tentativo di rivedere Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange potrebbe essere simile al seguente:
[TestMethod]
public void Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange()
{
// arrange
double beginningBalance = 11.99;
double debitAmount = 20.0;
BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);
// act
try
{
account.Debit(debitAmount);
}
catch (ArgumentOutOfRangeException e)
{
// assert
StringAssert.Contains(e.Message, BankAccount. DebitAmountExceedsBalanceMessage);
}
}
Rieseguire il test, riscrivere e rianalizzare
Quando vengono eseguiti nuovamente metodi di test con valori differenti, si verifica la situazione seguente:
Se si intercetta l'errore corretto usando un'asserzione in cui il valore debitAmount è superiore al saldo, l'asserzione Contains viene passata, l'eccezione viene ignorata, quindi viene passato anche il metodo.Questo è il comportamento desiderato.
Se si usa un valore debitAmount inferiore a 0, l'asserzione avrà esito negativo perché viene restituito il messaggio di errore errato.L'asserzione ha esito negativo anche se si introduce un'eccezione temporanea ArgumentOutOfRange in un altro punto nel metodo sotto il percorso del codice di test.Anche questo è valido.
Se il valore debitAmount è valido (ad esempio, minore del saldo, ma maggiore di zero, nessuna eccezione sarà intercettata, pertanto non verrà mai intercettata neanche l'asserzione.Il metodo di test passa.Questo non va bene perché si desidera che il metodo di test non riesca se non viene generata alcuna eccezione.
Il terzo fatto è un bug nel metodo di test.Per cercare di risolvere il problema, viene aggiunta un'asserzione Fail alla fine del metodo di test per gestire il caso se non viene generata alcuna eccezione.
Ma una nuova esecuzione mostra che a questo punto il test ha esito negativo se viene rilevata l'eccezione corretta.L'istruzione catch reimposta l'eccezione e il metodo continua a essere eseguito, ma non viene eseguito correttamente alla nuova asserzione.Per risolvere il nuovo problema, è necessario aggiungere un'istruzione return dopo StringAssert.La riesecuzione del test conferma che sono stati corretti i problemi.La versione finale di Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange sarà simile alla seguente:
[TestMethod]
public void Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange()
{
// arrange
double beginningBalance = 11.99;
double debitAmount = 20.0;
BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);
// act
try
{
account.Debit(debitAmount);
}
catch (ArgumentOutOfRangeException e)
{
// assert
StringAssert.Contains(e.Message, BankAccount. DebitAmountExceedsBalanceMessage);
return;
}
Assert.Fail("No exception was thrown.");
}
In questa sezione finale, le operazioni eseguite per migliorare il codice di test hanno portato a metodi di test più affidabili e informativi.Ma più importante, l'analisi aggiuntiva ha anche condotto a un codice migliore nel progetto sottoposto a test.