Lo Unit Testing con Visual Basic 2010
a cura di Alessandro Del Sole (requisiti: nessuno)

Introduzione
In questo articolo ci proponiamo di introdurre lo Unit testing dal punto di vista dello sviluppatore Visual Basic 2010 e di capire come possa essere utile per testare il codice che scriviamo senza dover ogni volta avviare l'applicazione. In modo particolare l'attenzione sarà concentrata sulla strumentazione di Visual Studio 2010, questo sia per favorire l'apprendimento da parte di coloro che non hanno mai fatto unit testing, sia perché molte persone ricorrono a strumenti esterni a Visual Studio senza sapere, o semplicemente senza approfondire, che l'IDE offre una serie di strumenti integrati a partire dall'edizione Professional.

Questa volta non c'è codice sorgente da scaricare, in considerazione del taglio step-by-step che viene dato alla trattazione.

Cos'è lo unit testing?
In breve, lo unit testing consente di testare il funzionamento di un blocco di codice 'astraendolo' da quello che è il suo contesto all'interno della nostra applicazione (concetti che a breve verranno spiegati con un esempio).
Tipicamente il codice sottoposto a unit testing è costituito da metodi che restituiscono qualcosa, quindi delle Function. Vediamo come funziona per poi dettagliarne gli aspetti.

L'esempio
In primo luogo, creiamo un progetto Visual Basic per la Console. Immaginiamo di voler creare un'applicazione che calcoli il prodotto di due numeri immessi dall'utente. Una cosa del tutto semplice, dovendo focalizzare l'attenzione sull'attività e non sulla complessità del codice.
L'utente immetterà due valori su nostra richiesta e poi verrà eseguito il calcolo:

  Sub Main()
    Console.WriteLine("Inserisci il primo numero:")
    ' Per praticit\uinput1\u224 à faccio una conversione diretta
    ' ma potrei utilizzare Double.TryParse per validazione
    Dim firstNumber As Double? = CDbl(Console.ReadLine)
    Console.WriteLine("Inserisci il secondo numero:")
    Dim secondNumber As Double? = CDbl(Console.ReadLine)
    Console.WriteLine("Il risultato \uinput1\u232 è {0}", Multiply(firstNumber,
                          secondNumber).ToString)
    Console.ReadLine()
  End Sub

Il metodo che esegue la moltiplicazione, chiamato con sforzo di fantasia Multiply, è decisamente semplice:

  Private Function Multiply(ByVal firstNumber As Double?,
                             ByVal secondNumber As Double?)
    If firstNumber.HasValue = False Then firstNumber = 0
    If secondNumber.HasValue = False Then secondNumber = 0
    Return firstNumber * secondNumber
  End Function

Ora supponiamo di voler eseguire lo unit testing di questo metodo. Posizioniamo il cursore sul nome del metodo, facciamo clic destro e scegliamo il comando Create Unit Tests:

Comparirà la finestra Create Unit Tests, dalla quale potremo selezionare le porzioni di codice da sottoporre a test. Per default, Visual Studio propone la selezione del metodo sul quale abbiamo avviato la procedura. Se provate ad espandere i vari nodi, noterete anche che è possibile testare proprietà e metodi di tipo Sub:

Assicuratevi che nella combobox sottostante sia selezionata la voce Create a new Visual Basic test project e poi fate clic su OK. Vi verrà poi richiesto di specificare un nome per il progetto di test, tramite la seguente finestra di dialogo:

E' probabile che vi verrà chiesto se volete accettare la visibilità di tipo Internal per i membri che sono marcati come Friend. Accettate questa richiesta e, dopo alcuni secondi, Visual Studio 2010 terminerà la generazione del progetto di test.
Nel codice di questo progetto, ci interessano principalmente due cose. Innanzitutto la classe, decorata con l'attributo TestClass proprio ad indicare che si tratta di un oggetto dedicato all'esecuzione dei test:

'''<summary>'''This is a test class for Module1Test and is intended
'''to contain all Module1Test Unit Tests
'''</summary><TestClass()> _
Public Class Module1Test

Poi la proprietà TestContext, che definisce il contesto di esecuzione dei test:

  Private testContextInstance As TestContext
  '''<summary>
  '''Gets or sets the test context which provides
  '''information about and functionality for the current test run.
  '''</summary>
  Public Property TestContext() As TestContext
    Get
      Return testContextInstance
    End Get
    Set(ByVal value As TestContext)
      testContextInstance = Value
    End Set
  End Property

La parte però di maggiore interesse è costituita da un metodo che Visual Studio ha chiamato MultiplyTest, decorato con l'attributo TestMethod, che è quello che effettivamente testa il nostro codice:

  '''<summary>
  '''A test for Multiply
  '''</summary>
  <TestMethod(), _
   DeploymentItem("ConsoleApplication1.exe")> _
  Public Sub MultiplyTest()
    ' TODO: Initialize to an appropriate value
    Dim firstNumber As Nullable(Of Double) = New Nullable(Of Double)()
     ' TODO: Initialize to an appropriate value
    Dim secondNumber As Nullable(Of Double) = New Nullable(Of Double)()
    ' TODO: Initialize to an appropriate value
    Dim expected As Object = Nothing
    Dim actual As Object
    actual = Module1_Accessor.Multiply(firstNumber, secondNumber)
    Assert.AreEqual(expected, actual)
    Assert.Inconclusive("Verify the correctness of this test method.")
  End Sub

Sostanzialmente per svolgere il nostro test dobbiamo, come indicato dai TODO:

  1. Specificare il primo numero della moltiplicazione
  2. Specificare il secondo numero della moltiplicazione
  3. Specificare il valore che ci attendiamo come restituito

Questo elenco potrebbe farci cadere in errore, poiché potrebbe far pensare che è talmente banale da rendere inutile lo sforzo di fare unit test. Proviamo però a pensare che al posto di numeri potrebbero esserci istanze di oggetti business popolati a runtime e quindi lo scenario può essere decisamente complesso. Facendo un discorso più generale, i punti 1 e 2 dovrebbero essere sostituiti con 'inizializzare le variabili facenti parte del test con valori che ci permettano di verificare il funzionamento del codice'. Modifichiamo quindi le prime righe del metodo MultiplyTest in questo modo:

    Dim firstNumber As Nullable(Of Double) = 3
    Dim secondNumber As Nullable(Of Double) = 2
    Dim expected As Object = 6
    Dim actual As Double?

Vogliamo in sostanza verificare che la moltiplicazione di 3 * 2 restituisca effettivamente 6. Nel caso ciò non avvenisse, vorrebbe dire che il codice che esegue il calcolo è sbagliato. Abbiamo anche sostituito il tipo Object con Nullable(Of Double). La riga Assert.Inconclusive può essere rimossa, poiché sta ad indicare che l'implementazione del metodo di test non è completa.
Per eseguire il test, dal menu Test selezioniamo Windows -> Test View. Questo farà sì che appaia la finestra ancorabile Test View:

Tale finestra contiene l'elenco di tutti i test method e possiamo avviare più test in un sol colpo. Per avviare il nostro, facciamo clic sul pulsante Run Selection. Dopo alcuni secondi saremo in grado di vedere il risultato nella finestra Test Results:

Ovviamente in un esempio semplice come il nostro, il test va a buon fine. Ci basta modificare uno dei valori nel codice (secondNumber=3) per vedere cosa succede quando il test fallisce:

Come vedete, la finestra Test Results mostra un messaggio di errore generato dal metodo Assert.AreEqual, indicando il valore atteso e quello effettivamente ottenuto. In tutto questo, vi sarete forse accorti che non abbiamo coinvolto l'applicazione originaria. Lo scopo è proprio quello di fare test di codice in modo indipendente dall'applicazione che usa il codice stesso (ecco perché si chiama 'unit').

Un rapido sguardo alla classe Assert
Come visto nella sezione precedente, il test vero e proprio viene posto in essere quando si invoca il metodo Assert.AreEqual che confronta i due valori. La classe Assert espone molti altri metodi condivisi per l'esecuzione di diverse tipologie di test, che ampliano gli scenari di test anche verso i tipi riferimento. Di seguito vengono riepilogati i più interessanti:

Attraverso questi e altri metodi, che è possibile conoscere agevolmente tramite l'IntelliSense mentre si richiama la classe Assert, è quindi possibile eseguire molte tipologie di test sia verso tipi valore che tipi riferimento.

Quanto codice è coinvolto nei test?
Sin dalle precedenti versioni Visual Studio offre uno strumento chiamato Code Coverage, che consente di determinare in che modo i vari blocchi di codice sono stati interessati dal test, fornendo il risultato sotto forma di valori percentuali. Per default il Code Coverage non è abilitato, quindi bisogna farlo a mano. Per prima cosa è necessario fare doppio clic sul file chiamato Local.testsetting in Solution Explorer quindi, nella dialog chiamata Test Settings, selezionare la scheda Data and Diagnostics, quindi apporre il segno di spunta sulla voce Code Coverage come evidenziato in figura:

C'è un altro passaggio da fare, ossia indicare al Code Coverage quali assembly devono essere interessati dal controllo. Facciamo quindi clic su Configure e selezioniamo l'assembly che definisce gli unit test, come rappresentato in questa figura:

Fatto questo possiamo rieseguire i test. E' poi la volta di aprire la finestra Code Coverage Results, tramite il comando Test|Windows, all'interno della quale potremo vedere in quale percentuale siano stati sottoposti a test i singoli membri del progetto di test.
Ad esempio, con riferimento al test esemplificativo proposto in questo articolo, si può osservare come il 77,78% del codice sia stato interessato dall'esecuzione dello unit test. Se poi si espandono i risultati, si può ancora osservare come il Getter della proprietà TestContext non sia stato interessato dal test, mentre gli altri membri sono stati interessati al 100%. Visual Studio offre anche una rappresentazione visiva del Code Coverage, che consiste nell'evidenziare con un colore diverso i blocchi di codice all'interno dell'editor. Per esempio, facendo doppio clic sul Getter della proprietà TestContext potremo osservare come questo sia evidenziato in rosso (che sta ad indicare il non coinvolgimento nel test), mentre il Setter sarà evidenziato in celeste molto chiaro e tale colore indica il pieno coinvolgimento del codice nello unit test.

Informazioni di dettaglio con IntelliTrace
Se avete letto questo mio precedente articolo, o se vi siete documentati per vostro conto, sapete che IntelliTrace è uno strumento, disponibile solo nell'edizione Ultimate di Visual Studio 2010, che permette di ottenere informazioni dettagliatissime su tutti gli accadimenti che si verificano in fase di debug, come errori o eventi. E' possibile utilizzare IntelliTrace anche quando si fa unit testing, in modo da poter ottenere informazioni di dettaglio su ciò che nei test ha dato problemi, sempre tenendo conto del fatto che questa tecnica richiede molte risorse di sistema. Per abilitare IntelliTrace ad intervenire durante l'esecuzione degli unit test è necessario riaprire la dialog chiamata Test Settings mediante doppio clic sull'elemento Local.testsettings in Solution Explorer, quindi selezionare ancora la scheda Data and Diagnostics, infine apporre il segno di spunta sulla casella chiamata IntelliTrace. Una volta abilitato, per capire come ci possa essere utile proviamo a causare il fallimento dello unit test precedente, cambiando uno qualunque dei valori numerici indicati. Dopo aver eseguito (e visto fallire) il test, nella finestra dei dettagli troveremo anche un link al file di log di IntelliTrace, con estensione .iTrace. Se apriamo questo file, Visual Studio mostrerà tutta una nutrita serie di informazioni, come ad esempio i thread coinvolti, il messaggio dettagliato dell'eccezione e tutta una serie di informazioni che ci possono permettere di risalire alla causa del problema. La seguente figura mostra un esempio del risultato che si ottiene e si capisce come sia possibile analizzare le singole eccezioni anche attraverso l'analisi dei singoli thread coinvolti:

Conclusioni
Molti definiscono lo unit testing un'arte e questa potrebbe essere una definizione appropriata, poiché tale pratica ci permette di testare il funzionamento di blocchi del nostro codice in maniera completamente svincolata dal contesto applicativo. Questo articolo non ha lo scopo di essere una guida esaustiva sullo unit testing, essendoci interi libri al riguardo, quanto piuttosto quello di introdurre la strumentazione nuova e quelle già esistenti per coloro che non hanno dimestichezza con la materia.
Per ulteriori e successivi contatti potete visitare il mio blog oppure scrivermi al mio indirizzo di posta.