NUnit

pubblicato il 24/06/2006

Test-Driven Development, per gli amici semplicemente TDD. La prima volta che ne ho sentito parlare ho pensato ad uno spray altamente nocivo contro gli insetti; in realtà si tratta di una pratica altamente benefica contro i bachi: mi sbagliavo, ma non troppo, in fin dei conti. Scherzi a parte, la TDD consiste nello scrivere contemporaneamente il codice "esecutivo" ed il codice di test; in altre parole mentre si scrive una routine, si scrive anche il codice che ne verificherà la correttezza dei risultati.

All'atto pratico, ciò si realizza creando due progetti: oltre a quello che contiene il codice da testare, si crea un secondo progetto che referenzia il primo e che contiene la classi di test delle classi del primo. Le classi di test normalmente derivano da classi definite in un apposito framework. Le classi di test realizzano il cosidetto black-box testing, ossia verificano che dato un determinato input si ottenga un determinato output; per ogni metodo della classe da testare vengono creati tanti metodi di test quante sono le possibilità di utilizzo dello stesso (parametri del metodo, proprietà della classe...).

In ambiente .NET esistono diversi framework per lo unit testing: in questo articolo discuterà di NUnit, framework che deriva il suo nome da JUnit, nato in ambiente Java. L'utilizzo è relativamente semplice e si realizza in due "passate": innanzitutto occorre creare il nuovo progetto di test (tipo Class Library), aggiungere tra i riferimenti l'assembly nunit.framework e quello da testare, nonchè altri eventualmente necessari a quest'ultimo. Tanto per esemplificare, diciamo che l'assembly da testare si chiama ' AssemblyA ed il progetto di test si chiama 'Test.AssemblyA'

A questo punto io normalmente aggiungo al progetto Test.AssemblyA una classe di test per ogni classe di AssemblyA; ad esempio TestClasseA sarà la classe di test della ClasseA (geniale, eh?!), TestClasseB sarà la classe di test della ClasseB e così via.

La classe di test deve essere composta in un certo modo: innanzitutto è consigliabile prima della definizione della classe aggiungere gli imports

Imports AssemblyA
Imports NUnit.Framework

(ovviamente l'ordine non è importante). Fatto ciò possiamo definire la classe, ma dobbiamo aggiungergli un attributo:

<TestFixture()> Public Class TestClasseA

che definisce la classe come classe di test. Vi sono poi due metodi che devono essere definiti:

<SetUp()> Public Sub Init()
...
End Sub

<TearDown()> Public Sub Destroy()
...
End Sub

Il metodo contrassegnato dall'attributo Setup serve per scrivere il codice di inizializzazione della classe (impostare variabii di istanza, ad esempio, opppure aprire una connessione a database), mentre quello contrassegnato dall'attributo TearDown serve per il codice di cleanup (chiudere la connessione al database, ad esempio); fatto ciò, la classe deve essere popolata dei metodi di test i quali devono essere contrassegnati dall'attributo Test:

<Test()> Public Sub AddSingleObject()
   ...
   Assert.IsTrue(test.Add(obj))
End Sub

<Test()> Public Sub AddException()
   Dim obj As New DivideByZeroException
   Assert.IsTrue(test.Add(obj))
End Sub

<Test()> Public Sub AddCollection()
   ...
   Assert.IsTrue(test.AddCollection(temp))
End Sub

Cominciamo a notare un po' di cose:

Come detto, l''attributo Test serve a dichiarare il metodo come un metodo da testare (poi vedremo come); notiamo poi che possiamo solo definire metodi senza parametri e senza valori di ritorno (niente Function, quindi, ma solo Sub); infine ogni metodo viene "chiuso" dalla chiamata di un metodo Assert.Qualcosa. Questi metodo servono per verificare il valore che viene restituito dai metodi della classe sotto test; in particolare:

AreEqual: verifica che i due valori passati come parametro siano uguali; nel caso di valori numerici è possibile anche definire un delta di tolleranza: se i due valori differiscono viene sollevata un'eccezione NUnit.Framework.AssertionException.

AreSome: verifica che i due oggetti passati come parametri siano lo stesso oggetto: se i due oggetti sono differenti, viene sollevata  un'eccezione NUnit.Framework.AssertionException.

IsFalse: verifica che il parametro passato valga False: in caso contrario, viene sollevata  un'eccezione NUnit.Framework.AssertionException.

IsTrue: speculare alla precedente, verifica che il parametro passato come parametro valga True: in caso contrario, viene sollevata  un'eccezione NUnit.Framework.AssertionException.

IsNull: verifica che l'oggetto passato come parametro sia Nothing: in caso contrario, viene sollevata  un'eccezione NUnit.Framework.AssertionException.

IsNotNull: speculare alla precedente, verifica che il parametro passato non sia Nothing: in caso contrario, viene sollevata  un'eccezione NUnit.Framework.AssertionException.

Si può infine interrogare la proprietà Assert.Counter per conoscere il numero di test eseguiti fino al momento.

La scrittura delle classi di test può richiedere diverso tempo e spesso (quasi sempre) la "cultura aziendale" dominante tende a classificarlo come "tempo prezioso perso" che è meglio dedicare alla scrittura di nuovo codice: l'ironia della sorte sta nel fatto che a codice non validato viene aggiunto ulteriore codice non validato; tradotto in soldoni, vuol dire aggiungere codice bacato a codice bacato quando con minor sforzo si sarebbe potuto consolidare il codice precedente, risolvendo i bachi prima che il programmatore abbia dimenticato quel codice per scriverne altro, prima che lo scopra il cliente e quando il tempo per la correzione è minore, così  come l'impatto della correzione (avanti: a chi non è mai capitato di introdurre un nuovo baco, correggendone uno vecchio? Magari in un punto insospettabilmente diverso dell'applicazione? Su, dài, non ci credo....)

Produttore: NUnit Development Team
Download: www.nunit.org/
Versione: 2.0
Prezzo: gratuito