Procedura dettagliata: creazione ed esecuzione di unit test per codice gestito
In questa procedura dettagliata verrà illustrato come creare, eseguire e personalizzare una serie di test di unità utilizzando il framework di test di unità di Microsoft per il codice gestito e l'Explorer di Test di Visual Studio.Sarà innanzitutto illustrato il progetto C# in fase di sviluppo, quindi saranno creati i test in cui sarà utilizzato il codice e saranno esaminati i risultati.Sarà infine possibile modificare il codice del progetto ed eseguire nuovamente i test.
Di seguito sono elencate le diverse sezioni di questo argomento:
Preparazione per la procedura dettagliata
Creare un progetto test di unità
Creare il primo metodo di test
Correggere il codice e rieseguire i test
Utilizzare test di unità per migliorare il codice
[!NOTA]
In questa procedura dettagliata viene utilizzato il framework di test di unità di Microsoft per il codice gestito.L'Explorer di Test può inoltre eseguire test da framework di unità di terze parti che hanno adattatori per l'Explorer di Test.Per ulteriori 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: utilizzo dell'utilità di test della riga di comando.
Prerequisiti
- Il progetto Bank.Vedere Progetto di esempio per la creazione di unit test.
Preparazione per la procedura dettagliata
Per preparare la procedura dettagliata
Aprire Visual Studio 2012.
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à utilizzato, 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 utilizzare a questo scopo.Nello spazio dei nomi per Bank, BankAccountNS, è presente la classe pubblica BankAccount, i cui metodi saranno testati nelle procedure seguenti.
In questo introduzione ci concentriamo sul metodo Debit. Il metodo Debit viene chiamato quando i soldi sono prelevati da un account e contiene il codice seguente:
// 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 test di unità
Prerequisito: attenersi alla procedura Preparazione per la procedura dettagliata.
Per creare un progetto di test di unità
Nel menu File, scegliere Aggiungi quindi scegliere Nuovo progetto ....
Nella finestra di dialogo del Nuovo progetto, espandere Installati, espandere Visual C# quindi scegliere Test.
Nell'elenco dei modelli, selezionare Progetto test di unità.
Nella casella Nome, immettere BankTest quindi scegliere OK.
Il progetto BankTests è 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 di gestione dei riferimenti, espandere Soluzione quindi selezionare l'elemento Bank.
Creare la classe di test
È necessaria una classe di test per verificare la classe BankAccount.È possibile utilizzare 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 Risorse.
Rinominare un file-classe
In Esplora soluzioni, selezionare il file UnitTest1.cs nel progetto BankTests.Dal menu di scelta rapida, scegliere Rinomina quindi rinominare il file in BankAccountTests.cs.Scegliere Sì nella finestra di dialogo in cui viene chiesto se rinominare tutti i riferimenti nel progetto dell'elemento con codice 'UnitTest1'.Questo passo cambia il nome della classe in BankAccountTest.
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 sotto test
È inoltre possibile aggiungere un'istruzione using alla classe per permettere di chiamare all'interno del progetto sotto test senza utilizzare nomi completi.All'inizio del file di classe, aggiungere:
using BankAccountNS
Requisiti della classe Test
I requisiti minimi per una classe di test sono i seguenti:
L'attributo [TestClass] è obbligatorio nel Framework per test di unità Microsoft per il codice gestito per qualsiasi classe che contiene metodi di test di unità che si desidera eseguire nell'Explorer di Test.
Ogni metodo di test che il test Esplora Risorse per eseguire deve essere impostato l'attributo di [TestMethod].
È possibile avere altre classi in un progetto di unit test che non presentano l'attributo di [TestClass] e possono contenere altri metodi nelle classi di test non presentano l'attributo di [TestMethod].È possibile utilizzare questi altri classi e metodi nei metodi di test.
Creare il primo metodo di test
In questa procedura, scriveremo i metodi di test di unità per verificare il comportamento del metodo Debit della classe BankAccount.Il metodo è elencato sotto.
Analizzando il metodo sotto test, determiniamo che esistono almeno tre comportamenti che devono essere controllati:
Il metodo genera un [ArgumentOutOfRangeException] se la quantità di credito è maggiore del bilancio.
Genera inoltre un ArgumentOutOfRangeException se la quantità di credito è minore di zero.
Se i controlli in 1.) e in 2.) sono validi, il metodo sottrae l'importo dal saldo del conto.
Nel primo test, verifichiamo che un importo valido (uno che sia minore del saldo del conto e che sia maggiore di zero) venga ritirato dall'account.
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.Installiamo un nuovo oggetto BankAccount con un bilancio iniziale e quindi ritiriamo un importo valido.Verrà utilizzato il framework di test di unità Microsoft per il codice gestito dal metodo AreEqual per verificare che il bilancio finale sia quello previsto.
Requisiti del metodo di test
Un metodo di test deve soddisfare i seguenti requisiti:
Il metodo deve essere decorato con l'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 elencato nel gruppo Test non eseguiti.Se l'Explorer di Test non viene visualizzato dopo una compilazione completata, scegliere Test dal menu, quindi scegliere Windows e quindi scegliere Test Explorer.
Scegliere Esegui tutto per eseguire il test.Mentre il test è in esecuzione la barra di stato nella parte superiore della finestra viene animata.Alla fine dell'esecuzione 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.particolare.Selezionare il metodo nell'Explorer di Test per visualizzare i dettagli nella parte inferiore della finestra.
Correggere il codice e rieseguire i test
Analizzare i risultati del test
Il risultato del test contiene un messaggio che descrive l'errore.Per il metodo AreEquals, il messaggio mostra cos'era previsto (il (parametro**<XXX> atteso**) e cìò che è stato ricevuto (l' Attualeparametro<YYY> ).é previsto il bilancio per rifiutare il saldo iniziale, ma viene aumentato l'ammontare della quantità da ritirare.
Il riesame del codice Debit mostra che il test di unità 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;
con
m_balance -= amount;
Eseguire nuovamente il test
Nel Explorer di Test, scegliere Esegui tutto per rieseguire il test.La barra verde/rossa diventa verde e il test viene spostato nel gruppo Test superati.
Utilizzare test di unità per migliorare il codice
In questa sezione viene descritto come un processo iterativo di analisi, di sviluppo di test di unità e del refactoring possono aiutare a rendere il codice prodotto più affidabile ed efficace.
Analizzare i problemi
Dopo aver creato un metodo di test per confermare che un importo valido venga correttamente detratto nel metodo Debit, è possibile tornare nei casi rimanenti nell'analisi originale:
Il metodo genera un ArgumentOutOfRangeException se la quantità di credito è maggiore del bilancio.
Genera inoltre un ArgumentOutOfRangeException se la quantità di credito è minore di zero.
Creare i metodi di test
Un primo tentativo di creare un metodo di test per indirizzare 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
}
Si utilizza l'attributo ExpectedExceptionAttribute per affermare che la giusta eccezione è stata generata.L'attributo causa la non riuscita del test a meno che non venga generato un ArgumentOutOfRangeException.Esecuzione del test con sia valori positivi e negativi di debitAmount quindi modificando temporaneamente il metodo sottoposto al test per generare una generica ApplicationException quando la quantità è inferiore a zero indicando così che il test si comporta correttamente.Per testare il caso quando l'importo ritratto è maggiore del bilancio, ciò che è necessario 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 con un numero maggiore del bilancio.
Eseguire i test
Eseguire i due metodi con valori diversi per debitAmount dimostrando che i test gestiscono in modo adeguato i casi rimanenti.Eseguire 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 problemi.Non è possibile essere certi di quale condizione nel codice sotto test generi l'eccezione quando entrambi i test sono in esecuzione.Qualche modo per differenziare le due condizioni sarebbe utile.Più pensiamo al problema, più diventa evidente che conoscere quale condizione è stata violata più aumenta la nostra 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 aiuterebbe, ma l'attributo ExpectedException non può fornire queste informazioni..
Analizzando di nuovo il metodo sottoposto a test, vediamo che entrambe le istruzioni condizionali usano un costruttore ArgumentOutOfRangeException che prende il nome dell'argomento come parametro:
throw new ArgumentOutOfRangeException("amount");
Da una ricerca di MSDN Library, individuiamo che esiste un costruttore che riporta informazioni molto più dettagliate.ArgumentOutOfRangeException(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 utilizzare questo costruttore.Ancor meglio, è possibile utilizzare membri di tipo pubblico per specificare gli errori.
Effettuare il refactoring del codice sotto test
Innanzitutto definiamo due costanti per i messaggi di errore a livello di classe:
// class under test
public const string DebitAmountExceedsBalanceMessage = "Debit amount exceeds balance";
public const string DebitAmountLessThanZeroMessage = "Debit amount less than zero";
Quindi modifichiamo 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 rimuoviamo l'attributo ExpectedException.Al suo posto, intercettiamo l'eccezione generata e verifichiamo che sia stata generata nell'istruzione di condizione corretta.Tuttavia, si deve decidere 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 (secondo parametro del costruttore ArgumentOutOfRangeException ) è maggiore del bilancio iniziale.Questa opzione richiede che testiamo la proprietà ActualValue dell'eccezione con la variabile beginningBalance del metodo di test e richiede quindi che ActualValue sia maggiore di zero.
Confermare che il messaggio (il terzo parametro del costruttore) includa DebitAmountExceedsBalanceMessage definito nella classe BankAccount.
Il metodo StringAssert.Contains nel framework di test di unità di Microsoft permette di testare la seconda opzione senza i calcoli necessari per la prima opzione.
Un secondo tentativo di verificare Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange potrebbe essere simile al seguente:
[TestMethod]
public void Debit_WhenAmountIsGreaterThanBalance_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);
}
}
Ritestare, riscrivere e rianalizzare
Quando ritestiamo i metodi di test con valori differenti, incontriamo i seguenti fatti:
Se intercettiamo l'errore corretto utilizzando debitAmount che è maggiore del bilancio, l'asserzione Contains passa, l'eccezione viene ignorata e pertanto il metodo di test passa.Questo è il comportamento che si vuole.
Se si utilizza debitAmount, l'asserzione avrà esito negativo perché viene ritornato il messaggio di errore errato.L'asserzione ha esito negativo anche se introduciamo un'eccezione temporanea ArgumentOutOfRange in un altro punto nel metodo sotto il percorso del codice di test.Anche questo è valido.
Se il valore di debitAmount è valido (ovvero, minore bilanciare ma maggiore di zero, alcuna eccezione non intercettata, pertanto asserzione non viene intercettata mai.Il metodo di test passa.Questo non va bene perché si vuole che il metodo di test fallisca 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 in cui non viene generata alcuna eccezione.
Ma riprovando mostra che l'esito del test è ora negativo se l'eccezione corretta viene intercettata.L'istruzione catch reimposta l'eccezione e il metodo continua a essere eseguito, fallendo alla nuova asserzione.Per risolvere il nuovo problema, si aggiunge un'istruzione return dopo StringAssert.Ritentando si conferma che sono stati corretti i problemi.La versione finale di Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange sarà simile alla seguente:
[TestMethod]
public void Debit_WhenAmountIsGreaterThanBalance_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, il lavoro che è stato eseguito sul codice di test ha portato a metodi di test più affidabili ed informativi.Ma più importante, le analisi aggiuntive hanno anche condotto ad un codice migliore nel progetto sottoposto al test.