Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Unit Test in ambiente ASP.NET con NUnit

Applicare il pattern "Four Phase Test" alle Web application, sfruttando il framework NUnit
Applicare il pattern "Four Phase Test" alle Web application, sfruttando il framework NUnit
Link copiato negli appunti

Nel mondo della programmazione, il termine Test assume una grandissima varietà di connotazioni spesso molto differenti tra loro, contribuendo quindi a generare una certa confusione sull'argomento. In generale il termine test applicato ad un prodotto software, indica un insieme di procedure atte a verificare che il software stesso soddisfi i requisiti voluti.

Tra tutte le metodologie di test lo Unit Testing merita una trattazione approfondita, perché tra tutte è quella più vicina allo sviluppatore. Le sue caratteristiche peculiari sono:

  1. Test scritti nello stesso linguaggio del programma
  2. Test automatizzati ed eseguibili tramite script
  3. Test focalizzati su piccole porzioni di codice
  4. Test scritti con un framework standard (xUnit)
  5. Uso di pattern standard per scrivere i test

Il punto tre in particolare è molto importante, uno unit test mira infatti a verificare il corretto funzionamento di una porzione di codice ridotta, esercitando quindi una singola micro funzionalità. L'obiettivo è quello di scrivere un grande numero di test, ed eseguirli il più spesso possibile per verificare che le modifiche fatte non abbiano introdotto errori. Il fatto di esercitare solamente una piccola parte del sorgente permette di individuare velocemente l'errore, spesso guardando solamente il nome del test che è fallito.

Il secondo punto di forza è l'automazione, grazie alla quale è possibile realizzare script che eseguono i test ad intervalli di tempo predeterminati, oppure ogni qual volta uno sviluppatore effettua un check-in.

Una modalità interessante è quella in cui la console di test rileva automaticamente la variazione dei compilati del progetto ed esegue i test ad ogni build riuscita. In questo modo è possibile modificare il codice, mantenendo nel contempo una buona confidenza riguardo la correttezza delle proprie modifiche. La possibilità di scrivere test nello stesso linguaggio del progetto permette inoltre una maggiore produttività, perché non è necessario apprendere nulla di nuovo per poter creare una suite di test.

In circolazione esistono molti framework che supportano la scrittura di unit testing e la scelta di quale utilizzare è spesso dettata da fattori di preferenze personali, dato che tutti condividono le stesse funzionalità di base. La difficoltà nello scrivere test unitari non è infatti assolutamente riconducibile alla conoscenza di una libreria piuttosto che un'altra, ma alle metodologie con cui i test stessi vengono costruiti.

Il pattern base di uno unit test è detto Four Phase Test, ovvero test in quattro fasi e rappresenta una struttura base che viene usata da tutti i framework in circolazione. Le fasi in questione sono

  1. Setup
  2. Exercise
  3. Verify
  4. TearDown

Nella prima fase viene impostata la fixture del test, termine che indica tutte le precondizioni che devono essere soddisfatte affinché il test possa essere eseguito. Esempi di fixture potrebbero essere: impostazione di parametri nella classe, inserimento di dati in un database, etc; in generale lo scopo del setup è assicurarsi che il test venga eseguito sempre con le stesse condizioni iniziali in modo che possa essere ripetibile.

Al termine della fase di setup si è dunque preparato l'oggetto del nostro test, chiamato generalmente SUT (System Under Test). Il SUT può essere un'istanza di una classe, o anche una semplice funzione e più in generale rappresenta il codice che vogliamo verificare; nella seconda fase si deve quindi solamente invocare il codice del SUT.

Il passo successivo è quello della verifica, in cui si effettuano delle asserzioni che costituiscono il nocciolo del test stesso. Una asserzione non è altro che una affermazione su una qualche proprietà del SUT o su eventuali valori di ritorno, con la quale vengono indicate al motore di test le condizioni che debbono essere verificate affinché il test sia considerato riuscito.

Per raggiungere questo obiettivo tutti i framework di unit testing mettono a disposizione apposite funzioni e classi per creare le asserzioni. Nell'ultima fase è necessario annullare tutte le eventuali modifiche fatte nel setup e dal SUT, in modo da riportare il sistema allo stato in cui era prima del test.

Per comprendere meglio la struttura di un test è necessario ora fare un esempio basato sul framework NUnit; considerando che ciò che verrà mostrato è applicabile comunque anche ad altri framework e più in particolare a tutti quelli racchiusi sotto la sigla generica xUnit.

Un test è un metodo di una classe, decorato con particolari attributi, che contiene uno scheletro di test.

Esempio di test

using NUnit.Framework;

namespace UnitTest
{
  [TestFixture]
  public class SampleTestFourPhases
  {
    [TestFixtureSetUp]
    public void TestFixtureSetUp()
    {
      Console.WriteLine("TestFixtureSetUp");
    }

    [TestFixtureTearDown]
    public void TestFixtureTearDown()
    {
      Console.WriteLine("TestFixtureTearDown");
    }
   
    [SetUp]
    public void TestSetUp()
    {
      Console.WriteLine("TestSetUp");
    }

    [TearDown]
    public void TearDown()
    {
      Console.WriteLine("TearDown");
    }
		
    [Test]
    public void Test1()
    {
      Console.WriteLine("Test1");
    }
		
    [Test]
    public void Test2()
    {
      Console.WriteLine("Test2");
    }
  }
}

L'attributo TextFixture indica che la classe contiene dei test, il TestFixtureSetUp indica il metodo che deve essere eseguito per primo, il metodo TestFixtureTearDown è invece quello eseguito per ultimo. Gli attributi SetUp e TearDown invece marcano due metodi che vengono eseguiti rispettivamente prima e dopo ogni test.


I test infine non sono altro che i metodi marcati con l'attributo Test; nell'esempio precedente la sequenza dei metodi chiamati è:

  1. TextFixtureSetUp
  2. SetUp
  3. test1
  4. TearDown
  5. SetUp
  6. test2
  7. TearDown
  8. TextFixtureTearDown

Per eseguire i test NUnit in maniera semplice è possibile installare un tool chiamato testdriven.net, presente sia in una versione a pagamento, sia in una versione "community" utilizzabile da chi sviluppa software open source. Questo tool permette di cliccare con il tasto destro direttamente sul codice del test ed eseguirlo internamente a Visual Studio.

Se non si vogliono istallare tool aggiuntivi si può semplicemente aprire la console grafica di NUnit, creare un nuovo progetto e, tramite il menu project:AddAssembly caricare la dll che contiene i test. In figura è mostrato come appare la console di test quando si carica l'assembly UnitTest.dll compilato dal progetto accluso.

Figura 1. Console di test NUnit
Console di test NUnit

Nella parte sinistra viene mostrata la lista di tutti i test che NUnit ha trovato nell'assembly, raggruppati per namespace. Premendo il tasto run viene eseguito il ramo di test selezionato, permettendo quindi di eseguire selettivamente un sottoinsieme dei test presenti. Durante l'esecuzione per ogni test viene indicato il fallimento o il successo con un icona rispettivamente verde o rossa. Nella parte destra viene invece mostrato l'output e i dettagli specifici riguardo ai test che sono falliti.

Ora che si possiede una conoscenza basilare dell'ambiente NUnit, è necessario dare un piccolo insieme di ulteriori regole che dovrebbero essere seguite per scrivere test in ambiente ASP.NET.

La pagina default.aspx, presente nel progetto accluso, effettua una semplice operazione di addizione tra numeri interi con una rudimentale validazione. Il primo e fondamentale fattore da notare, è che in una applicazione ASP.NET, il codice scritto direttamente nella pagina non può essere sottoposto facilmente a unit testing, dato che non è possibile effettuare un riferimento ad un progetto Web.

La prima regola in ASP.NET per realizzare codice che possa essere sottoposto a unit testing, è quindi quella di tenere tutta la logica applicativa in un progetto separato.

Nella pagina Page1.aspx è stata spostata l'operazione di business (l'addizione tra interi) in un progetto esterno chiamato DomainLogic nel file Domain1.cs. Grazie a questa accortezza si può ora scrivere il seguente test.

[TestFixture] 
public class TestPage2Logic {
  
  [Test]
  public void TestAdd() {
    Assert.AreEqual(10, DomainLogic.Domain.Add(7, 3)); 
	}
}

Data la banalità della logica da verificare, il test sembra superfluo, ma lo scopo di questo esempio è di spiegare come strutturare la propria applicazione affinché si possano scrivere unit test in maniera efficace.

La verifica della correttezza dell'operazione viene effettuata mediante il metodo Assert.AreEqual, fornito da NUnit, che permette di asserire l'eguaglianza tra due quantità. Se una sola asserzione di un test fallisce, tutto il test viene considerato fallito.

Nella pagina page2.aspx si è effettuato un ulteriore passo avanti ed anche la validazione è stata spostata nell'assembly DomainLogic. La validazione dei parametri è infatti una operazione fondamentale, soprattutto per questioni legate alla sicurezza applicativa e quindi dovrebbe essere anche essa sottoposta a test.

Ecco quindi come scrivere i test per verificare la validazione:

[Test]
public void TestValidation() {
  Assert.That(DomainLogic.Domain2.ValidateInput("56", "56"), Is.Empty);
}

[Test]
public void TestValidationWrong1() {
  Assert.That(DomainLogic.Domain2.ValidateInput("56f", "56"), Is.Not.Empty);
}

[Test]
public void TestValidationWrong2() {
  Assert.That(DomainLogic.Domain2.ValidateInput("56", "56t"), Is.Not.Empty);
}

In questo caso è stata utilizzata una sintassi differente per le asserzioni, basata sul metodo Assert.That, che accetta due metodi, la variabile da verificare (in questo caso il valore di ritorno dell'operazione ValidateInput) e un oggetto di tipo Constraint che permette di esprimere asserzioni in maniera molto chiara.

Se leggiamo in inglese il terzo test otteniamo «Assert that return value of ValidateInput("56", "56t") is not empty». Questo test è basato sul fatto che il metodo ValidateInput restituisce una stringa di errore in caso di valori non corretti e quindi passando un valore che non è convertibile a intero (56t) la stringa ritornata non deve essere vuota.

Il concetto chiave è che tutto ciò che è logica applicativa non deve assolutamente essere incluso nella pagina aspx, ma spostato in progetti esterni in modo da poter essere esercitato con appositi test. Questa separazione inoltre permette di avere un progetto più strutturato e quindi più mantenibile.

Ti consigliamo anche