Test Against Database

La scorsa settimana durante l'incontro di UgiAlt.net abbiamo parlato di unit test e discutendo diquesto tema si finisce sempre per andare a toccare uno dei temi più insidiosi e che spesso allontanano gli sviluppatori dalla scrittura dei test: i test di integrazione con il database.

Chiunque abbia provato a scrivere test si è scontrato con l'implementazione di un test che deve andare a scrivere su una tabella di un database che ha un numero elevato di foreign key. La scrittura di questi test ha in genere un notevole overhead per portare il db in uno stato consinstente prima di poter eseguire il vero test.

Quali approcci possiamo usare?

Gli approcci che ho provato e ho trovato più efficaci sono due.

Il primo consiste nel avere un database di test in uno stato noto e usare la convenzione che ci sono già alcuni dati noti che possiamo usare nei test. Cosi se ad esempio dobbiamo inserire un indirizzo possiamo basarci sul fatto che la tabella delle provincie contiene già il record relativo alla città di Brescia e che il test conosce l'id di questo record:

 

[Test] public void SaveAddress_CreateANewValidAddressAndSaveItOnDatabase_ShouldSucced() { AddressService as = IoC.Resolve<AddressService>(); Address a = new Address(); a.Street = "via Mantova 6"; a.City = GetBrescia(); as.Save(a); // ...Assert e verifca } private City GetBrescia() { return new City(Id=5, Name="Brescia"); }

L'ipotesi è molto forte e può sembrare troppo vincolante e difficile da rispettare, soprtattutto quando il database è grosso e cambia spesso durante lo sviluppo. Per questi motivi talvolta è meglio fare in modo che il database venga ricostruito ad ogni avvio di sessione di test utilizzando una serie di script TSQL che costruiscono le tabelle e inseriscono i dati per i test. Quando l'applicazione ha raggiunto una certa maturità si può utilizzare anche un backup da restorare ad ogni sessione.

 

Un secondo approccio consiste nel fare in modo che ogni test si occupi di portare il database in uno stato consistente per se stesso. Sempre in riferimento all'esempio di prima il test dovrebbe inserire la città di Brescia nella tabella delle città ottenendo ogni volta un nuovo Id (fornito dal DB) da utilizzare nella costruzione dell'oggetto City per il test sull'indirizzo.

La differenza con l'approccio precedente è che in questo secondo caso i test sono indipendenti dai dati presenti nel database e ogni test (o ogni fixture) è responsabile dei suoi dati che dovrà inserire durante il setup e rimuovere durante il teardown.

Questo approccio è più oneroso in termini di codice da scrivere ma è migliore dal punto di vista della pulizia. Tramite opportuni metodi di helper è facile scrivere le query necessarie all'inserimento dei record necessari ai test.

In entrambi i casi è importante che i test siano "educati" e rimuovano tutti gli eventuali record inseriti per evitare che altri test vengano influenzati dalla presenza di dati sporchi nel database.

Print | posted on Sunday, March 2, 2008 8:35 PM