Ricerca 
it-ITen-US
Registrazione
Accedi
 
Autore:  Sabrina Cosolo
Pubblicato il:  2007/12/26

Unit Testing del codice (parte 1)
Le basi per la costruzione di test per rendere più solido il nostro codice

In questo articolo, esploreremo le basi dello Unit testing, cercando di incuriosire e invogliare i programmatori ad usarli.

Gli Unit test sono uno strumento importantissimo per scrivere codice solido e per risparmiarci il classico problema che regolarmente si verifica quando rilasciamo una nuova versione del codice. Ovvero, aggiungiamo delle cose nuove che rendono l'applicazione migliore ma in compenso alcune cose vecchie che funzionavano, smettono di funzionare.

Usando gli Unit Test, non possiamo evitare del tutto questo tipo di problema, ma possiamo ridurlo drasticamente.

All'interno di questo articolo, utilizzeremo NUnit, un framework Open Source scaricabile gratuitamente che funziona con tutte le versioni di Visual Studio .NET.

In futuro, probabilmente riproporremo gli stessi test utilizzando in modo diretto il sistema di Testing fornito da Visual Studio .NET 2008 in versione professional.

Introduzione

Chi come me è un dinosauro della programmazione che ha iniziato con il C negli anni ottanta, quando il solo sistema di sviluppo concesso ai più fortunati era il PE 2 di IBM oppure il Norton Editor, sa come, per sviluppare e testare il codice man mano che viene scritto è necessario crearsi una applicazione di test ove verificare le varie funzionalità di ciascuno dei pezzetti scritti. Ai tempi in cui io ho iniziato era una bella applicazione console che faceva un sacco di printf, quelli che adesso si chiamano Console.Writeline. E se qualche dinosauro bene informato obietta che ai tempi avevamo il mitico Microsoft Codeview, replico che è vero, ma se il programma occupava la maggior parte dei canonici 512Kb di Ram, la compilazione con i simboli di debug non poteva essere fatta e quindi Codeview non partiva quindi printf rimaneva lo strumento principe. Scrivere una applicazione di test non è qualcosa di semplice ne di immediato, infatti per testare una classe di Business Logic simile a quelle che abbiamo scritto nella serie di articoli Fritto Misto, bisogna creare un progetto winforms, generare almeno una form, creare dei bottoni, aggiungere textbox e altri controlli e scrivere poi il codice di test.

Lo si fa la prima volta che si genera la classe, poi, quando si modifica per aggiungere funzionalità, la si testa direttamente sull'applicazione per cui si è fatta la modifica, senza scrivere un nuovo test, perché "Tanto Funziona tutto", inoltre ci rompe le scatole generare un nuovo bottone, aggiungere una textbox, oppure fare una nuova opzione di menu e generare una form solo per testare uno stupido metodo. E chi afferma il contrario ha il naso lungo come Pinocchio!!!

Questo non è il miglior modo di procedere, ma alzi la mano il programmatore che non è in ritardo sulla consegna o quello che il giorno prima di una consegna non ha mai ricevuto cinque richieste da parte del commerciale che sono indispensabili perché il prodotto sia consegnabile e vendibile.

Gli Unit test, e NUnit,  per quanto necessitino che noi scriviamo del codice non banale, ci vengono incontro fornendoci già di per se un'interfaccia utente standard per la generazione di test, che viene gestita da NUnit, lasciando a noi il solo compito di dover creare i metodi che testano le nostre classi, non dobbiamo generare form, ne bottoni, ne menu, solo il codice per i test.

Per chi voglia davvero approfondire l'argomento Unit test, consiglio la lettura del libro Pragmatic Unit Testing in C# with NUnit di Andrew Hunt e David Thomas, che fa parte della serie dei Pragmatic Books, iniziata con The Pragmatic Programmer uno dei libri che chiunque voglia divenire programmatore professionista dovrebbe leggere, perché è fra i pochi libri che non scrivono codice, ma insegnano concetti e metodologie che non hanno versione ne scadenza.

Tutte le cose interessanti che troverete qui, le ho imparate dal primo libro che ho citato, tutti gli errori, orrori, omissioni sono invece di mia esclusiva produzione.

Che cos'è lo Unit Testing?

Uno unit test, è una porzione di codice che uno sviluppatore scrive per "allenare" una piccola e specifica area funzionale del codice che viene testato. Solitamente, uno Unit Test esegue un particolare Metodo in uno specifico contesto. Ad esempio, aggiungere un elemento ad una collezione e verificare che l'elemento sia davvero stato aggiunto alla collezione stessa. Oppure rimuovere dei caratteri da una stringa e verificare che non ci sono più.

Uno unit test viene eseguito per essere certi che una porzione di codice esegue quello che noi vogliamo. Se tutti gli Unit test di una classe danno esito positivo, potremo pertanto essere certi di aver "detto" al compilatore  la cosa giusta da fare, è infatti un Assioma il fatto che un computer fa sempre ciò che gli diciamo, mai ciò che vogliamo e l'utopia del cursore addomesticato, salvo in Star Trek, resta ancora tale.

Ovviamente, gli Unit Test non sono i soli test che provano che una applicazione funziona, infatti si limitano a fornire la certezza della correttezza di un algoritmo, non certo sono in grado di verificare che la logica funzionale dell'applicazione sia adatta agli utenti che ne fruiranno oppure che il funzionamento dell'applicazione intera sia quello che il cliente si aspetta. Per questi aspetti servono sempre dei tester umani, però, un buon Unit Testing può risparmiare al programmatore un fine settimana di lavoro straordinario perché da un cliente è stato utilizzato un valore non previsto in un metodo noto ed il programma è esploso.

Usando gli Unit Test saremo certi che le porzioni di codice relative alla business logic e alla manipolazione di dati funzionino, e soprattutto, potremo predisporre un sistema per cui con un singolo tasto Run ripetiamo i test in tutte le nostre classi, e possiamo scoprire se una modifica alla classe A è andata a sconvolgere il funzionamento della classe X istantaneamente.

Ci sono una serie di domande da porsi a cui è necessario rispondere quando si inizia a pensare di utilizzare gli unit test, ne vedremo alcune e daremo delle risposte, che ovviamente potreste non condividere.

A che cosa serve lo Unit Testing? A rendere la vita del singolo programmatore e dei membri di un team più facile.

Che cosa fornisce? La risposta alla domanda: Il codice fa quello che io volevo? Se la risposta è SI, possiamo andare oltre e sviluppare nuove cose, se la risposta è NO dobbiamo rivedere il codice fino a raggiungere la risposta SI.

Che cosa dobbiamo testare? Ovviamente non solo i valori che noi ci aspettiamo di ricevere ma dobbiamo testare i casi limite, i valori errati, in questo modo potremo costruire codice più forte, stabile e pulito, perché sappiamo come gestire le emergenze.

Ci sono dei benefici ulteriori che danno valore aggiunto a questa metodologia? Si, per esempio, gli Unit Test sono un metodo molto semplice per mostrare come il nostro codice va utilizzato a chi lo dovesse usare in seguito, anche a noi stessi quando torneremo a guardarlo dopo qualche mese oppure qualche anno.

Come si fa? bisogna prima decidere in che modo testare la porzione di codice che si desidera "allenare", ovviamente esistono delle linee guida in merito, deciso come testare non resta che scrivere il codice necessario ed eseguirlo. Possibilmente bisogna sempre eseguire tutti i test di tutte le classi di un progetto non solo i test appena aggiunti per le nuove porzioni di codice. Questo per accorgersi se le modifiche appena effettuate in una classe avessero mandato in crash il codice di qualche altra porzione del progetto. Se vi fossero dei test molto lunghi, ovviamente è opportuno lanciarli di sera per verificarne i risultati la mattina dopo, oppure usare una macchina predisposta per questo tipo di operazioni (ovviamente quest'ultimo scenario ha senso in progetti estesi e complessi).

In che cosa consistono gli Unit Test

Uno unit test è ovviamente un metodo che si trova in una classe, per lavorare con NUnit, la classe ed i metodi di test devono essere scritti secondo alcune regole specifiche, molto semplici, che, utilizzando degli Attributi per Decorare classi e metodi, istruiscono NUnit riguardo a quello che troverà e come dovrà usare le nostre classi di test.

Il codice di uno Unit Test, solitamente sarà composto dal necessario a generare l'oggetto da testare e da una serie di righe di codice che utilizzano la classe Assert di NUnit.

I metodi primari della classe Assert

//C#
//Verifica se due oggetti sono uguali
//Il metodo fornisce overload per tutti i tipi di dati base
//del framework più l'Object
Assert.AreEqual( expected, actual[, message]);
//Per i numeri in virgola mobile o i decimali la tolleranza
//permette di definire un Delta entro cui l'uguaglianza è accettabile.
Assert.AreEqual( expected, actual, tolerance[, message]);
//Verifica se un oggetto è nullo
Assert.IsNull( object[, message]);
//Verifica che un oggetto non sia nullo
Assert.IsNotNull( object[, message]);
//Verifica se due variabili contengono un reference allo
//stesso oggetto.
Assert.AreSame(expected, actual, tolerance[, message]);
//Verifica se una condizione è vera
//La condizione può essere qualsiasi espressione con risultato booleano.
Assert.IsTrue( bool condition[, message]);
//Verifica se una condizione è falsa
Assert.IsFalse( bool condition[, message]);
//Interrompe l'esecuzione dei test
Assert.Fail(message);
'VB
'Verifica se due oggetti sono uguali
'Il metodo fornisce overload per tutti i tipi di dati base
'del framework più l'Object
Assert.AreEqual( expected, actual[, message])
'Per i numeri in virgola mobile o i decimali la tolleranza
'permette di definire un Delta entro cui l'uguaglianza è accettabile.
Assert.AreEqual( expected, actual, tolerance[, message])
'Verifica se un oggetto è nullo
Assert.IsNull( object[, message])
'Verifica che un oggetto non sia nullo
Assert.IsNotNull( object[, message])
'Verifica se due variabili contengono un reference allo
'stesso oggetto.
Assert.AreSame(expected, actual, tolerance[, message])
'Verifica se una condizione è vera
'La condizione può essere qualsiasi espressione con risultato booleano.
Assert.IsTrue( bool condition[, message])
'Verifica se una condizione è falsa
Assert.IsFalse( bool condition[, message])
'Interrompe l'esecuzione dei test
Assert.Fail(message)

Questi sono alcuni fra i più utilizzati metodi per generare asserzioni in uno Unit test, certamente ne incontreremo altri e vi invito ovviamente a fare un giro sull'intellisense della classe Assert per saperne di più.

La struttura di una classe di Unit test

using NUnit.Framework;

namespace Dnw.Base
{
    [TestFixture]
    public class StringHelperTest
    {


    }
}
Imports NUnit.Framework 

Namespace Dnw.Base 
     _ 
    Public Class StringHelperTest 
        
        
    End Class 
End Namespace

La struttura di base di una classe di Unit test è quella di una normale classe, includiamo il namespace NUnit.Framework per permetterci di utilizzare le classi di questa libreria. Ovviamente nel nostro progetto di Unit test (separato dalla libreria con le classi da testare) faremo un reference alla Libreria di NUnit.

La sola differenza da una classe da noi generata normalmente è l'attributo TextFixture, questo attributo è interpretato da NUnit come identificativo di una classe che deve elaborare come classe di test.

//Inizializzazione generale per tutti i test
[TestFixtureSetUp]
public void ClassInit()
{

}

//Inizializzazione prima di ogni singolo test
[SetUp]
public void Init()
{
}

//Metodo di test con categoria che ne indica il gruppo di appartenenza
[Test]
[Category("StringHelperTest")]
public void T10StringHelperCompare()
{
}

//Metodo di reset alla fine di ogni singolo test
[TearDown]
public void Endit()
{
}

//Metodo di reset alla fine dei test dell'intera classe
[TestFixtureTearDown]
public void ClassEndit()
{
}
'Inizializzazione generale per tutti i test 
 _ 
Public Sub ClassInit() 
    
End Sub 

'Inizializzazione prima di ogni singolo test 
 _ 
Public Sub Init() 
End Sub 


'Metodo di test con categoria che ne indica il gruppo di appartenenza 
 _ 
"StringHelperTest")> _ 
Public Sub T10StringHelperCompare() 
End Sub

'Metodo di reset alla fine di ogni singolo test 
 _ 
Public Sub Endit() 
End Sub 


'Metodo di reset alla fine dei test dell'intera classe 
 _ 
Public Sub ClassEndit() 
End Sub

Il codice qui sopra riporta un esempio di come può essere strutturata una classe di Unit Test. I nomi dei metodi non hanno alcuna importanza, mentre importantissimi sono gli attributi che ciascuno di essi ha. Vediamo che cosa significano:

  • TestFixtureSetUp Indica il metodo di inizializzazione dell'intera serie di test, in questo metodo ad esempio si potrebbe inizializzare una stringa di connessione a database, oppure dei dati che sono costanti per tutti i test.
  • SetUp Indica il metodo che verrà eseguito prima di ciascuno dei metodi di test inizializzando tutto ciò che è l'ambiente per il test. Ad esempio, in questo metodo si potrebbe instanziare la classe da testare in modo da essere certi che ogni test ha una classe completamente pulita su cui lavorare.
  • Test Indica un metodo che effettua un test.
  • Category Indica una categoria in cui raggruppare i test, si può raggrupparli per zona di appartenenza, per tipo di test o quel che si preferisce, in questo modo, NUnit inserirà i test in una treeview che ci permetterà di lanciare o trovare i test quando ne avremo scritti molti.
  • TearDown Indica il metodo che alla fine di un test resetta l'ambiente, ad esempio chiude connessioni, socket, resetta buffer di lavoro comuni eccetera.
  • TextFixtureTearDown Indica il metodo di reset finale per l'intera serie di test, si può quindi usarlo per fare un clear di risorse e dati ecc. ecc.

I test e le eccezioni

Ovviamente, testando una classe, una delle cose che potrebbe essere utile testare sono i casi in cui si verifica un'eccezione, NUnit fornisce un modo per testare anche le eccezioni.

//Metodo di test con categoria che ne indica il gruppo di appartenenza 
[Test(), ExpectedException(typeof(ApplicationException))] 
[Category("ExceptionsTest")] 
public void T10Eccezione() 
{ 
}
'Metodo di test con categoria che ne indica il gruppo di appartenenza 
GetType(ApplicationException))> _ 
"ExceptionsTest")> _ 
Public Sub T10Eccezione() 
End Sub

Il test qui sopra scritto permette di verificare che un metodo venga mandato in eccezione e restituisca il tipo di eccezione che noi ci aspettiamo.

Se invece si verificassero delle eccezioni inaspettate, non serve far nulla perché NUnit ci segnalerà l'errore e ci mostrerà tutto ciò che riguarda l'eccezione che si è verificata.

Ignorare un test

//Metodo di test con ignore
[Test(), Ignore("Il metodo da testare non è pronto")] 
[Category("ExceptionsTest")] 
public void T10TestMetodoX() 
{ 
}
'Metodo di test con ignore 
"Il metodo da testare non è pronto")> _ 
"ExceptionsTest")> _ 
Public Sub T10TestMetodoX() 
End Sub

Se siamo così bravi da scrivere i test prima di aver implementato una classe oppure se vogliamo disattivare i test mentre facciamo delle modifiche ad una classe e i nostri colleghi devono usare i test ma riceverebbero degli errori, possiamo inserire l'attributo Ignore che salta il test e informa chi lo usa del motivo per cui è stato disattivato.

L'interfaccia di NUnit, ci evidenzia i test Ignorati in Giallo in modo che non ci scordiamo di riattivarli.

Come decidere i test da effettuare

Abbiamo visto come scrivere un test, perché scrivere un test, ma che tipi di test dobbiamo fare sulle nostre classi?
Ci sono vari tipi di test che si possono fare ovviamente, e dipendono da che cosa fa il nostro metodo, elenchiamoli:

  • Correttezza - Il risultato è corretto?
  • Limiti - (boundaries) In caso eventuali valori e parametri raggiungano i limiti massimi e minimi si verifica la condizione di correttezza?
  • Inversi - Si può controllare relazioni inverse?
  • Controllo incrociato - Si può ottenere lo stesso risultato con altri mezzi e il valore restituito dal nostro metodo è sempre coerente?
  • Errori - E' possibile verificare le condizioni di errore?
  • Performance - Il metodo come risponde in caso di carico di lavoro pesante?

Per creare dei test significativi, un buon metodo per impostare più valori per un test potrebbe essere leggere i valori da un file, in questo modo potremmo avere dati di limite, dati di base, molti dati per i test di performance eccetera.

Test dei limiti

Com'è fatto un test di limite? Passare dati totalmente errati, passare dati malformattati, passare parametri nulli o vuoti, passare dati maggiori del massimo ragionevole atteso. Inserire duplicati ove non dovrebbero esserci, dare liste non ordinate quando aspettate e viceversa. Cercare di compiere azioni in ordine sbagliato. Ad esempio usare un metodo prima di valorizzare una property ad esso necessaria.

La linea guida per le condizioni di limite potrebbe essere la seguente:

  • Conformità - il valore è conforme ad un formato prestabilito?
  • Ordinamento - una lista di valori è ordinata oppure disordinata come previsto?
  • Gamma - il valore appartiene al campo compreso fra un ragionevole minimo e massimo?
  • Riferimenti - Il codice referenzia qualcosa di esterno che non è sotto il suo diretto controllo?
  • Esistenza - Il valore esiste (non null, non zero, all'interno di una lista predefinita ecc.)
  • Cardinalità - Ci sono un numero sufficiente di valori?
  • Tempo (assoluto e relativo) Tutto accade in un ordine prestabilito, al momento giusto e in un tempo stabilito?

Test delle relazioni inverse

Non sono applicabili a tutti i metodi, ma se lavoriamo con dei valori e dato un valore X otteniamo A applicando ad A la formula inversa otteniamo di nuovo X? Ad esempio se un metodo genera il quadrato di un numero, la sua radice quadrata rigenera il valore originale?.

Test di controllo incrociato

Se ad esempio è necessario un metodo che somma dei dati e fa delle valorizzazioni, verificare che con un metodo diverso i risultati siano comparabili.

Test di controllo Errori

Se vi sono condizioni di errore gestite, forzare questo tipo di situazione, ad esempio un disco pieno, la rete che si sconnette e simili. Dato che non sempre è possibile fare verificare condizioni di questo genere, ci sono degli oggetti che si chiamano Mock Objects ovvero degli oggetti che fingono di essere qualcosa che non sono per provocare errori.

Non sono parte di questo articolo, se ne siete interessati vi consiglio la lettura del libro Pragmatic Unit Testing in C# with NUnit.

Test di performance

In caso di oggetti che manipolano dati, si connettono a database, contengono collezioni di dati, è opportuno effettuare dei test di carico per verificare che funzionino correttamente indipendentemente dalla quantità di dati che contengono e che non mettano in crisi le risorse della macchina che li ospita quando contengono grandi quantità di dati. Anche in questo caso i test possono necessitare di dati esterni, ad esempio un database piuttosto che un file di testo contenente dati da inserire ecc.

Come sono fatti dei buoni test

Se siete dei Fan di Harry Potter, e anche se non lo siete, credo sappiate che un potente incantesimo usato male può fare più male che bene, gli Unit Test possono essere paragonati agli incantesimi, se usati correttamente sono un aiuto potente, se usati malamente potrebbero fare danni perché principalmente sarebbero una perdita di tempo prezioso per il programmatore che tempo non ne ha mai a sufficienza.

Pertanto, se gli Unit Test non sono scritti ed implementati in modo corretto, potreste perdere più tempo a farne manutenzione e debug che a scrivere codice di produzione.

Ovviamente non lascieremo che ciò accada visto che la ragione primaria per usare gli Unit Test è rendersi la vita più facile. Ci sono quindi delle semplici linee guida da seguire per tener lontani i problemi dai nostri progetti

Dei buoni test hanno le seguenti proprietà e sono:

  • Automatici
  • Esaustivi
  • Ripetibili
  • Indipendenti
  • Professionali

Automatici

Ogni Unit Test non deve necessitare di un essere umano che prema un bottone per essere eseguito. Il solo intervento umano necessario dev'essere il click del bottone Run su NUnit oppure il task che lancia gli unit test deve poter essere lanciato automaticamente da un Job Batch.

Se un test necessita di dati di input dev'esserci un mezzo automatico che li fornisce, file di testo, database o altro.

Se siamo un Team di programmatori e tutti devono poter lanciare i test, facciamo in modo che i dati siano parte del progetto di test oppure, se sono in grande quantità, che si trovino in un luogo a tutti raggiungibile (es. una cartella condivisa su una rete mappata per tutti i programmatori allo stesso modo.)

Automatico significa inoltre che il test deve sapere da solo se è andato bene oppure male e non deve bloccare l'esecuzione dei test che lo seguono se non va a buon fine ma deve fornire informazioni utili a chi esaminerà il file di log dei test per le verifiche dovute.

NUnit ci aiuta in questo senso, ma anche i nostri test devono fornire messaggi facilmente comprensibili a chi controlla i test.

Esaustivi

Non significa che devono portare il programmatore all'esaurimento (fisico e nervoso), bensì che devono testare tutto ciò che potrebbe rompersi. Qui, entra in gioco il buon senso, si potrebbe testare ogni linea di codice, ma ricordiamoci le due domande primarie a cui un programmatore deve rispondere con il suo lavoro:

A cosa serve?
Il cliente lo paga?

Quindi il buon senso è d'uopo, cerchiamo di creare dei test che controllino il buon funzionamento nell'uso corrente di una funzionalità, che testino le situazioni limite, che testino performance e magari, se necessario facciamo dei test con valori a campione, ma ricordiamoci che dobbiamo anche produrre.

Ci sono leggi matematiche (per me molto vicine agli incantesimi di Harry Potter) che dimostrano come i Bug non sono distribuiti uniformemente nel codice. Se vi accorgete che in una porzione di codice trovate delle porcherie, Buttatelo via e riscrivetelo, perderete molto meno tempo che cercando di rappezzarlo. Quel codice potrebbe essere stato scritto male perchè scritto in fretta, per necessità di un momento senza poter pensare all'intero contesto di funzionamento, pertanto quando lo prendete in mano, prendetevi un attimo, pensate alla prima domanda scritta qualche riga qui sopra e poi, senza paura, buttate via tutto e riscrivetelo, sapendo cosa volete ottenere scriverete codice migliore e porbabilmente più performante che tentando di inserire delle patch in del codice costruito su un problema probabilmente non chiaro.

Ripetibili

Ogni test deve essere indipendente da tutti gli altri, devono essere indipendenti anche dal sistema in cui girano. Il Top sarebbe che ogni test possa essere ripetuto all'infinito senza bisogno di modifiche all'ambiente che lo produce.
Ovviamente non è sempre possibile, infatti, i test che lavorano su Database hanno qualche difficoltà a poter essere eseguiti, resettare il database e ripartire. Anche in questo caso, bisogna avere sempre il Buon Senso di pensare a come coniugare la ripetibilità con il carico di lavoro per noi e per le macchine. Ad esempio, partire sempre da un backup del database per lanciare i test a vuoto, creare dei finti database caricati sul momento, creare dei Mock Objects (che non trattiamo perché non ne abbiamo il tempo ovviamente.). Se un test non è ripetibile, è inutile, pertanto pensiamoci sempre quando scriviamo un test.

Indipendenti

Ogni test deve essere una scatolina a se stante, non deve essere una matrioska che dipende da altri test. Ogni test deve iniziare fare ciò che serve e terminare, senza necessitare di dati da un'altro test o simili. Più pulito e più semplice è un test, più facile è manutenerlo, scriverlo, verificarlo ed usarlo. Ecco perché in NUnit esistono i metodi di setup per ogni test (attributi Setup, TearDown), che permettono di predisporre l'ambiente per ogni test e resettarlo alla fine.

Professionali

Il codice che scriviamo per uno Unit Test non deve essere scritto diversamente dal codice che scriviamo per il programma stesso, le regole di scrittura, i commenti, l'uso di classi, sintassi, metodologie di controllo, non deve differire da quello usato per il codice normale perché lo Unit Test è parte del nostro normale codice sviluppato, è parte integrante di un progetto e serve a renderlo più forte e stabile. Perciò non cambiamo stile di scrittura se stiamo facendo uno Unit Test, scriviamo magari una classe generica o un metodo se vediamo che abbiamo del codice che è ripetuto in più test diversi, creiamo un metodo parametrico, non facciamo copia e incolla tanto è solo un test.

Ricordiamoci che la manutenzione e il debug lo dovremo fare anche sugli Unit Test.

Come si corregge un Bug

Correggere un Bug quando si usano gli unit test è una cosa semplice ma il bug può essere usato per rafforzare il nostro codice se procediamo in questo modo:

  1. Identifichiamo il Problema
  2. Scriviamo uno Unit Test che lo provoca e verifichiamo la sua esistenza.
  3. Modifichiamo il codice affinché il problema sia risolto.
  4. Verifichiamo che tutti gli Unit Test su quella classe o su quel metodo passino correttamente.

Se utilizziamo questo meccanismo di Risposta al Mondo reale, difficilmente un Bug corretto si ripresenterà e difficilmente la correzione di un bug ne introdurrà un altro.

Quando si correggono Bug, un'altra domanda a cui rispondere è la seguente:

Ma il problema corretto potrebbe verificarsi da qualche altra parte?

Abituiamoci a pensare sempre al contesto dell'intero progetto, non concentriamoci sul singolo pezzetto di codice, eviteremo di ritrovare un Bug corretto in un punto che si ripresenta in un altro punto qualche giorno dopo.

Dove si mette il codice di test?

Il codice di test è opportuno scriverlo all'interno di un progetto a se stante, NUnit fornisce già un template che permette di generare un progetto in Visual Studio 2005, se necessario, all'interno del codice sarà opportuno inserire qualche specifico codice, ma ricordiamoci che l'Incapsulamento di OOP non deve essere spezzato per effettuare dei test.

Una cosa utile da farsi potrebbe essere, quando si lavora in team, far testare il proprio codice da un'altro programmatore perché potrebbe rilevare cose a cui non abbiamo pensato.

Ricordiamoci sempre che tutti i test devono essere eseguiti correttamente sempre, se stiamo sviluppando una modifica, una volta terminata e testata, lanciamo sempre un test globale per verificare se abbiamo rotto qualcosa in altro luogo.

Gli Unit Test per qualsiasi porzione di codice dovrebbero essere eseguiti con regolarità, per verificare sempre e comunque che tutto sia a posto. Non illudiamoci che una volta testato un progetto tutto funzionerà sempre, soprattutto se si tratta di un progetto complesso ove ogni progetto referenzia altri progetti e librerie esterne alla soluzione.

Test nuovi per codice vecchio

E' ovvio che scoperti gli Unit Test e la loro funzionalità non possiamo di punto in bianco smettere di produrre per un mese per andare a scrivere Unit Test per tutto il vecchio codice, iniziamo a farlo per tutto il codice che scriviamo da ora in avanti, però, quando prendiamo in mano una vecchia classe, che non ha test, è il momento buono per perderci sopra un po' di tempo a scrivere test, in questo modo potremmo rilevare Bug nascosti e risolverli prima che si verifichino in produzione.

Conclusioni

Ho esposto un sacco di teoria e niente pratica, la pratica la faremo insieme nella parte 2 scrivendo i test per alcune delle classi della serie Fritto Misto. Questo articolo non vuol essere certo una trattazione esaustiva ed esauriente dell'argomento Unit Testing, piuttosto un modo per incuriosire ed avvicinare i miei colleghi a questo modo di lavorare non serve a lavorare di meno, anzi spesso bisogna lavorare di più, però si lavora meglio, si produce meglio e probabilmente ci si stressa molto meno. Ovviamente ci vuole tempo e ci vuole disciplina, ma come sempre, chi più spende, meno spende.

Per qualsiasi Feedback, Ulteriore domanda, Chiarimento, oppure se trovate qualche errore usate direttamente il form dei commenti in calce a questo articolo oppure scrivete all'autrice .



       
Articoli|Webcast|Risorse|Utility
© 2007-2010 by DotNetWork  .:.  Condizioni d'uso  .:.  Privacy  .:.  Accedi  .:.