Visual Studio 2005 e unit test

Dopo circa un mesetto di utilizzo di tecnologia targata Microsoft volevo condividere qualche punto.

Visual Studio 2005 è un gran bell’ IDE, molto integrato, facile da usare, user-friendly e molto potente; C# d’altronde è molto simile a Java come linguazzo OO, con delle feauture molto interessanti.

La prima pecca che ho potuto notare è che non ha nessun framework integrato per fare unit test, e questo è male!!  O meglio ne esiste solo una versione che integra un ambiente per i test unitari (la Team Edition).

L’inizio dello sviluppo è stato molto pittorico: molti “just click next” e poco codice a manina,  e quindi non era una priorità testare unitariamente il codice prodotto dal framework .NET.

In questa ultima settimana fortunatamente avendo preso un pò il controllo degli strumenti e padroneggiando di più gli strati di codice prodotti dal magnifico “maghetto” targato M$, ho ripreso a essere io il protagonista dello sviluppo, e per ovvi motivi mi sono sentito in braghe di tela quando non potevo fare test unitari.

Quindi ho speso una giornata a cercare e valutare la configurazione migliore in Visual Studio 2005 per crearmi un ambiente adatto e confortevole per raggiungere questo obiettivo.

Sono partito da NUnit che è il porting di JUnit per .NET. Bisogna scaricarsi le librerie e poi creare una solution che faccia riferimento a nunit.framework.

Il core di NUnit è molto interessante e assomiglia a quello di JUnit 4 in cui i Test sono evidenziati tramite le assertion; qua invece che le assertion si usano altre strutture che consentono di decorare con dei metadati le classi di Test:

    [TestFixture]
public class BankAccountTest

[SetUp]
public void Init()
{
bankAccount = new BankAccount(“Mazi”);
}

[Test]
public void InitTest()
{
Assert.AreEqual(0.0, bankAccount.Balance);
}

Come si può vedere si usa la direttiva TextFixture per definire una classe di Test, e altri attributi specifici per Test, SetUp, TearDown ecc. ecc.

NUnit non è integrato in Visual Studio, e quindi una volta creata la solution di test (la dll o l’exe) bisogna usare la gui di NUnit standalone per eseguire i test: questo può risultare scomodo per chi è abituato a un ambiente orientato ai test unitare come Eclipse.

Purtroppo nel modo M$ ci sono, oltre a NUnit, altri vari framework di unit test:

  1. CSUnit
  2. Zanebug
  3. MBUnit

Ognuno ha i suoi vantaggi, e ognuno dice di essere il migliore😀

Per ora proverò l’accoppiata NUnit e TestDriven.Net, diciamo che il desiderata sarebbe avere test unitari e un plugin per Visual Studio integrato in modo da non dover usare un programma esterno per testare, ma fare tasto destro, run test🙂

Mazi

5 Responses

  1. Sono uno studente di ingegneria informatica. Sto affrontando un progetto in questi giorni riguardante i mock objects. Volevo porvi alcune domande.

    Sto esaminando la problematica dei test; quando noi esaminiamo, testiamo, codice che lavora su un database ed esegue su di esso delle operazioni e delle modifiche rischiamo che queste modifiche mettano a rischio l’integrità e la consistenza del mio database. A questo punto un successivo test potrebbe darmi esito negativo anche senza errori nel codice o nella struttura del mio applicativo ma per modifiche avvenute sui dati del mio db “sbadatamente” durante un test precedente.
    Bene, volevo sapere se era possibile, tramite l’utilizzo di mock objects, realizzare delle copie di un database (da “buttar via” alla fine del test stesso) sulle quali far lavorare i miei test in modo tale da salvare l’integrità del mio database principale.

    sapreste dirmi inoltre se c’è del materiale riguardante questi aspetti scaricabile in rete, oppure se esistono siti che descrivono meglio queste procedure?

    Ho bisogno di aiuto si tratta della mia tesi….
    Vi prego. Grazie infinite.
    filippo

  2. Ciao Filippo.
    Quello che hai appena toccato è un tema caldo su Internet, uno scoglio contro cui tutti quelli che intraprendono TDD si devono scontrare.
    Come conciliare unit-testing con data-access layer?

    Prima di tutto voglio confermarti che non esiste un metodo perfetto per realizzarlo.

    Il primo approccio da me provato quando lavoravo su tecnologia Java con un ORM (Hibernate) è stato quello classico di utilizzo di uno schema reale per i test unitari: approccio totalmente perdente, per vari svantaggi.

    I test unitari dovrebbero avere meno barriere possibili ed essere completamente autonomi da una loro esecuzione anche al “buio della tua cameretta”: per i DB relazionali queste barriere sono rappresentate dal fatto che il servizio potrebbe non essere running (magari l’RDBMS gira su una macchina in rete, mentre noi stiamo eseguendo i test completamente fuori rete) e dal disallineamento del tuo modello dati dallo schema che utilizzi per testare.
    Per queste ragioni sarebbe sempre meglio isolare il codice di accesso al DB in uno strato esterno dal domain model (chiamiamolo data-access layer) e testare il più possibile senza DB.

    E’ anche vero che “l’occhio” vuole la sua parte, e ho sempre avuto la convinzione che testare un DAO senza controllare “visivamente” ogni tanto i dati su DB per vedere che effettivamente il giro fosse completo non mi dava la certezza che tutto stesse funzionando.

    Riporto parte di un post di un blog di un amico che toccava l’argomento:

    A test is not a unit test if:

    1. It talks to the database
    2. It communicates across the network
    3. It touches the file system
    4. It can’t run correctly at the same time as any of your other unit tests
    5. You have to do special things to your environment (such as editing config files) to run it.

    Tests that do things things aren’t bad. Often they are worth writing, and they can be written in a unit test harness. However, it is important to be able to separate them from true unit tests so that we can keep a set of tests that we can run fast whenever we make our changes.

    Quindi per raggiungere questo stato senza rasentare la perfezione (Don’t strive for perfection, good is just enough) ci sono due semplici strade:
    1) Usare un Embedded Database
    2) Organizzare una architettura che renda possibile astrarsi dal DB.

    Pre condizione di queste scelte è avere un ORM di supporto (e.g. Hibernate per Java, Nhibernate per C#).
    Per la prima scelta ti consiglio di leggere questi due articoli, uno per Java e l’altro per C#.

    La seconda strada (che è quella che attualmente utilizzo) è una soluzione intermedia che ti permette di usare sempre lo schema che usi correntemente (quindi senza avere l’overhead di configurare un ambiente appositio senza per altro rischiare di sporcare i dati con delle operazioni DML verso il DB.
    L’idea alla base di questa è che tutti i test verso il data-access layer ereditino da una classe di Test che definisce il setup e il teardown in modo da iniziare una transazione e poi farne il rollback: in questo modo tutte le scritture su DB sono garantite dal non sporcare i dati presenti, quello di cui bisogna preoccuparsi è sempre la lettura di cui bisogna avere un minimo di manutenzione.

    Di seguito l’esempio del test unitario che definisce il SetUp e il TearDown

    namespace Tests.DataAccess
    {
    public class NHibernateTestCase
    {
    ///
    [TestFixtureSetUp]
    public void Setup() {
    XmlConfigurator.Configure();
    NHibernateSessionManager.Instance.BeginTransaction();
    }

    ///
    /// Properly disposes of the .
    /// This always rolls back the transaction; therefore, changes never get committed.
    ///
    [TestFixtureTearDown]
    public void Dispose() {
    NHibernateSessionManager.Instance.RollbackTransaction();
    }
    }
    }

    E un esempio di test unitario per un DAO (che tratta oggetti di tipo File).

    namespace Tests.DataAccess
    {
    [TestFixture]
    [Category("Database Tests")]
    public class FileDaoTest : NHibernateTestCase
    {
    private static IDaoFactory daoFactory = new NHibernateDaoFactory();
    private static IFileDao fileDao = daoFactory.GetFileDao();

    [Test]
    public void TestSave()
    {
    File file = new File();
    file.NomeFile = "locaholhost.test.it";
    file.Descrizione = "Descrizione";

    File fileSaved = fileDao.Save(file);
    Assert.AreEqual(file, fileSaved);
    }
    }
    }

    Come puoi ben immaginare alla fine dell’esecuzione il DB rimane nella stessa situazione precedente al test.
    Ovviamente questa soluzione non risolve le letture, i cui test prendono come prerequisito di avere i dati che si cercano su DB.

    .......
    [Test]
    public void TestGetById()
    {
    logger.Info("Executing TestGetById with FileID 2");
    File file = fileDao.GetById(2, false);
    Assert.IsNotNull(file);
    }
    .......

    Qua ad esempio ipotizzo che esista un file con chiave 2 sulla tabella File. Avendo però realizzato un sistema in cui il modifiche al DB sono rollbackate, ci si mette al riparo dalla situzione in cui un test possa lasciare dati sporchi che potrebbero influenzare un test di lettura.

    Saluti
    Mazi

  3. ciao mazi,
    dove inserisco nel codice che hai scritto qui sopra il metodo da testare?
    inoltre, ho delle query scritte in sql, come faccio ad inviarle al mio db tramite nhibernate, all’interno del test?
    in pratica, il codice che mi hai proposto è semplice e bello anche da vedere ma non riesco a capire come inserirlo nel contesto che sto esaminando. scusa la mia ignoranza.
    ti ricordo che lavoro in visual studio.
    devo ringraziarti perchè il tuo post è finalmente qualcosa di veramente diretto al mio problema dopo giorni e giorni di ricerca. Se potessi aiutarmi sulle questioni che ti ho proposto mi saresti di grandissimo aiuto.
    ciao grazie mille per la tua disponibilità

  4. mazi??? help me… ho bisogno di aiuto…

  5. Ciao, visto che l’interesse suscitato coincide con lo sviluppo di un piccolo template per offrire un servizio di e-commerce, allego la prima parte di questo framework, in cui è presente un abbozzo di domain-model, data-layer e unit-test.

    La solution per Visual Studio 2005 è scaricabile tramite questo file. Una volta scaricato il file bisogna aprirlo con winrar ed estrarne il contenuto.

    Il modello dati come illustrato in questo schema è molto semplice e si trova nel project Ecommerce.Core.

    La parte di data-layer è integrata nel project Ecommerce.Data, mentre quella di unit-test è in Ecommerce.Tests.

    Per ogni DAO (presente nel data-layer) esiste una classe di test unitari per le funzionalità base (caricamento e salvataggio).

    Per eseguire i test all’interno di Visual Studio è possibile utilizzare il menù contestuale di Resharper oppure usare NUnit.

    All’interno della classe CustomerDaoTest c’è anche uno unit-test per eseguire query SQL come da te richiesto Filippo.

    Tutta la solution è stata sviluppata nei ritagli di tempo tra ieri e oggi, prendendo gran parte del codice dal progetto di backoffice che sto gestendo attualmente.
    Ti consiglio spassionatamente di leggere questo documento che mi è servito come riferimento nella implementazione dell’attuale progetto su cui lavoro.

    Mazi

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: