Introduzione al Test Driven Development con Visual Basic 2010
a cura di Alessandro Del Sole (requisiti: aver letto l'articolo sullo Unit Testing))

Introduzione
In un precedente articolo abbiamo discusso lo unit testing in Visual Basic 2010 e abbiamo visto come questa pratica consenta di analizzare il comportamento di porzioni di codice al di fuori del contesto dell'applicazione, raggiungendo un importante livello di astrazione.

La pratica dello unit testing è anche alla base di un più ampio approccio allo sviluppo chiamato Test Driven Development (abbreviato in TDD). Tramite il TDD, gli sviluppatori creano le applicazioni partendo dallo scrivere unit test; una volta che questi vengono superati con successo, il loro codice viene rielaborato e trasferito al progetto vero e proprio. Questo meccanismo può sembrare inutile o macchinoso, in realtà non è così e più che altro è una filosofia completamente diversa di affrontare lo sviluppo. Scrivere unit test su cui costruire l'applicazione è un'ottima pratica, perché si verifica che il codice funzioni prima di darlo "in pasto" all'applicazione risparmiando tempo, quindi, nei test successivi allo sviluppo.

In questo articolo, che riprende analoga trattazione in inglese sul mio libro Visual Basic 2010 Unleashed, ci proponiamo di dare uno sguardo a questo modo di pensare al codice e, in modo più particolare, alla nuova strumentazione che Visual Studio 2010 mette a disposizione al riguardo. Ci renderemo conto che si tratta di strumenti già noti, rivisti in un'altra ottica.
Il codice a corredo dell'articolo è disponibile in area Download di Visual Basic Tips & Tricks.

In cosa consiste
Il Test Driven Development si distingue in tre momenti fondamentali, noti come Red -> Green -> Refactor:

  1. Red: lo sviluppatore inizia con lo scrivere gli unit test da zero; tipicamente questi falliranno, per cui restituiranno un risultato rosso (Red)
  2. Green: lo sviluppatore si concentra sul codice e lo migliora al punto tale che gli unit test vengono superati con successo. L'esecuzione positiva dei test in Visual Studio restituisce un risultato verde (Green)
  3. Refactor: lo sviluppatore rielabora e riorganizza il codice muovendolo dallo unit test al progetto, riproponendolo in modo più pulito e facendo le necessarie correzioni.

Vedremo ora in pratica, attraverso un esempio, come utilizzare gli strumenti di Visual Studio 2010 nei confronti di un approccio orientato al TDD.

Creazione di un progetto di esempio
In Visual Studio 2010 l'approccio al Test Driven Development si realizza mediante l'aggiunta di un cosiddetto Test Project alla soluzione; tale progetto di test è quindi relativo al progetto vero e proprio per l'applicazione.
Per fini dimostrativi, quindi, creiamo una nuova Class Library in Visual Basic 2010, chiamata Rectangle. Quando il progetto è visibile in Solution Explorer, rimuoviamo il file Class1.vb generato di default. Prima di scrivere il codice per la libreria, creiamo il nostro test project di modo che possiamo sfruttare l'approccio al Test Driven Development. Quindi:

  1. selezioniamo File -> Add -> New Project
  2. Nella finestra di dialogo di creazione progetto, cerchiamo la cartella di modelli chiamata Test Projects, quindi espandiamola al fine di recuperare il nodo Test Documents, selezionandolo con un clic.
  3. scegliamo il modello Test Project, chiamandolo RectangleTest.

Tutti i passaggi sono riepilogati in figura:

Il progetto di test così creato è però vuoto. Il primo passaggio da compiere è quello di aggiungere una classe, che chiameremo RectangleTest.vb, al cui interno scriveremo gli unit test.

Scrivere gli unit test
Ipotizziamo che la nostra libreria originaria esporrà una classe che rappresenti un rettangolo, con proprietà come altezza, larghezza e metodi per il calcolo dei valori relativi alla figura geometrica. Sappiamo che il Framework espone una sua classe Rectangle, ma scriverne una personalizzata è una facilitazione per comprendere i concetti sul TDD. Il progetto di test ci serve quindi per testare il codice prima che venga applicato alla nostra classe Rectangle, quindi dobbiamo lavorare sulla classe RectangleTest creata poc'anzi. Le classi di test devono essere decorate con l'attributo Microsoft.VisualStudio.TestTools.UnitTesting.TestClass e, fortunatamente, il namespace è importato di default per cui la dichiarazione è semplificata in questo modo:

<TestClass()>
Public Class RectangleTest

End Class

Tale attributo farà si che Visual Studio riconosca la classe come un luogo in cui risiedono unit test. In modo simile, i metodi che costituiscono gli unit test devono essere decorati con l'attributo TestMethod. Possiamo immaginare che nella classe definitiva avremo un metodo che calcoli il perimetro del rettangolo. Scrivendolo in un progetto di test, la sua dichiarazione si presenta così:

    <TestMethod()>
    Sub CalculatePerimeter()

    End Sub

Ora spostiamoci nel corpo del metodo e scriviamo questa riga:

Dim rect As New Rectangle

Poiché il tipo Rectangle nel namespace dell'applicazione non è definito, Visual Studio 2010 lo evidenzia come inesistente, mostrando le varie possibilità di correzione:

Stiamo quindi per riprendere lo strumento Generate From Usage introdotto proprio in Visual Studio 2010, di cui parlammo in un vecchio articolo, e che costituisce, in realtà, lo strumento primario che l'IDE offre per l'approccio al TDD. Facciamo quindi clic sulle opzioni di correzione e, nel pop-up che appare, scegliamo Generate New Type. La figura seguente mostra il risultato da raggiungere:

E' fondamentale sottolineare che il nuovo tipo Rectangle non viene aggiunto al progetto di test, ma al progetto principale. La ragione è semplice: in uno scenario dimostrativo come questo si sta lavorando con un solo tipo e quindi costerebbe poca fatica crearlo nel progetto di test e poi portarlo nel progetto primario, ma in scenari reali si ha a che fare con grandi quantità di tipi personalizzati e quindi l'approccio proposto è più snello. In sostanza, nel modo proposto si mantengono i benefici del TDD e si implementano i tipi direttamente nel progetto che ne farà uso effettivo. Ora la precedente dichiarazione va riscritta in questo modo, proprio perché il tipo è esposto altrove:

Dim rect As New Rectangle.Rectangle

Continuiamo la scrittura del nostro unit test aggiungendo la seguente riga, che assegna la larghezza del rettangolo:

rect.Width = 150

Chiaramente tale proprietà non esiste ancora nella classe Rectangle, per cui VS 2010 la evidenzia come inesistente e ripropone l'utilizzo del Generate From Usage per la sua generazione:

Selezioniamo Generate Property Stub for 'Width' di modo che la proprietà venga aggiunta alla classe; tra l'altro Visual Studio riconosce il tipo numerico e dichiarerà la proprietà con il tipo corretto. Analogamente va generata la proprietà relativa all'altezza, quindi partiamo scrivendo l'assegnazione:

rect.Height = 100

per poi utilizzare la tecnica descritta in precedenza per generarla. A questo punto possiamo completare la scrittura dello unit test scrivendo codice tipico di questi contesti, ossia codice che confronti il valore atteso a quello restituito dal calcolo e il cui esito sia restituito da un'asserzione. Il metodo va quindi completato in questo modo:

        Dim expected = 500
        Dim result = rect.CalculatePerimeter

        Assert.AreEqual(expected, result)

Si ha quindi un valore atteso (expected) e un risultato (result) che viene restituito da un metodo chiamato CalculatePerimeter che non esiste ancora. Sfruttando nuovamente Generate From Usage, lo creiamo con un semplice clic accettando la prima soluzione proposta.

Red
A questo punto, seguendo le istruzioni imparate nel precedente articolo, mandiamo in esecuzione lo unit test, che fallirà miseramente:

Non deve sorprenderci questo: siamo infatti nella fase Red del TDD. La ragione del fallimento sta nel fatto che il metodo CalculatePerimeter, a seguito dell'automatismo offerto da Generate From Usage, risulta così definito:

    Function CalculatePerimeter() As Object

        Throw New NotImplementedException
    End Function

Green
La fase successiva al fallimento del test ci richiede le modifiche strutturali che ne consentano il superamento. Modifichiamo quindi il metodo in questo modo:

    Public Function CalculatePerimeter() As Integer
        Return (Width * 2) + (Height * 2)
    End Function

Probabilmente ora starete obiettando che le funzioni di calcolo matematico lavorano con i Double e non con gli interi. Questo è vero, ma le ragioni sono due: la prima è che vogliamo rispettare l'implementazione automatica fatta da Visual Studio 2010 per le proprietà, di tipo Integer; la seconda, più importante, è che tali modifiche vanno fatte nella fase successiva. Per ora eseguiamo nuovamente lo unit test, per verificare che questa volta passerà con successo.

Refactor
Una volta che ci siamo assicurati che gli unit test siano funzionanti, possiamo riorganizzare il codice nella fase finale del TDD. In particolare, le modifiche da fare sulla classe Rectangle riguardano i tipi numerici utilizzati e la leggibilità del codice che, in definitiva, può essere riscritto in questo modo:

Public Class Rectangle

    Public Property Width As Double
    Public Property Height As Double

    Public Function CalculatePerimeter() As Double

        Dim sumOfWidth As Double = Me.Width * 2
        Dim sumOfHeight As Double = Me.Height * 2
        Dim perimeter As Double = sumOfHeight + sumOfWidth

        Return perimeter
    End Function
End Class

Ora che abbiamo fatto le necessarie modifiche, sappiamo che il codice funzionerà: lo abbiamo infatti già testato in fase di sviluppo e questo ci eviterà di testarlo ancora in fase di debug dell'applicazione, facendoci risparmiare molto tempo.

Conclusioni
Il Test Driven Development è una vera e propria filosofia di vita e di sviluppo del software. Ci sono interi libri su tale materia, nel presente articolo l'intento era mostrare la strumentazione offerta da Visual Studio 2010 per questa pratica e come ancora una volta, semmai ce ne fosse bisogno, tutto sia applicabile con successo anche a Visual Basic.
Per ulteriori informazioni potete contattarmi attraverso il mio blog o il mio indirizzo di posta elettronica.