Tester une couche d'accès aux données : une approche
Le problème
Appliquer des tests unitaires à une couche d'accès aux données peut se révéler être une tâche plus ardue qu'il n'y parait au premier abord. Dans la plupart des cas, il va s'agir d'amener la base de données dans un état présentant l'ensemble des conditions de départ du test, puis de lancer le code à tester et enfin de vérifier les conditions finales.
Pour que ces tests soient répétables, il faut évidemment que les conditions soient similaires à chaque lancement du test. On peut imaginer plusieurs solutions pour répondre à ce besoin (liste non exhaustive):
- Réinitialiser la base de données avant chaque test, en la remplissant avec un jeu de données aussi exhaustif que possible. On s'arrange dans ce cas pour que les conditions de départ des tests soient remplies directement par ce jeu de données et on maintient le jeu de données en même temps que le code et les tests.
- Créer un nouveau jeu de données (de nouvelles lignes avec de nouveaux identifiants) avant chaque test à l'aide d'un script SQL. Nettoyer la base après les tests en priant pour que le code de cleanup ne soit pas buggé (ou créer des tests unitaires pour tester le code de cleanup des tests unitaires :)
- Combiner les deux approches
Dans tous les cas, on se trouve dans une situation assez difficile à maintenir. Soit on maintient une base avec un jeu de test exhaustif, soit on maintient des scripts et du code de cleanup. Or, c'est un fait, le développeur n'aime pas maintenir. C'est long, fastidieux et franchement pas très intéressant. La question est donc de savoir si on ne pourrait pas faire sans, tout en conservant les qualités intrinsèques d'un bon test unitaire.
Pour résumer, on voudrait pouvoir se mettre dans des conditions de test et garder l'environnement propre avec le minimum d'effort.
Une solution
En attendant de trouver mieux, l'approche que j'ai retenue pour mes tests avec le framework de tests unitaires de Visual Studio 2005 , c'est ça :
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.EnterpriseServices;
namespace MesTests
{
[TestClass]
[Transaction(TransactionOption.Required, Timeout = 300)]
public class TestMaClasse : ServicedComponent
{
public TestMaClasse()
{
}
[TestInitialize()]
public void Initialize()
{
}
[TestCleanup()]
public void Cleanup()
{
ContextUtil.SetAbort();
}
[TestMethod]
public void MethodeDeTest()
{
DAL.EcritDesDonnees();
DAL.LitDesDonnees();
Assert.IsTrue(...);
// Autres vérifications...
}
...
}
}
Ma classe de test dérive de ServicedComponent, c'est donc un composant COM+. Avec l'attribut TransactionAttribute, j'indique que je travaille en mode transactionnel. Dans le TestCleanup, j'annule la transaction. En pratique donc mes tests travaillent sur la base de données autant qu'ils le souhaitent. A la fin tout est annulé "magiquement" par le rollback de la transaction COM+ et on revient à l'état initial avant les tests. Pour éviter d'utiliser des scripts SQL, j'utilise ma couche d'accès aux données pour écrire les données requises pour le test. Si vraiment ce n'est pas possible, je passe par un script SQL mais dans la majorité des cas, ce n'est pas nécessaire.
Notez aussi le "Timeout=300" qui laisse le temps de debugger tranquillement avant la fin automatique de la transaction. Le délai par défaut est un peu court et la transaction est souvent annulée alors qu'on est en plein debug, c'est assez pénible.
Ca pourrait peut-être marcher avec NUnit, mbUnit,
Nouveaux problèmes
La solution n'est pas parfaite pour plusieurs raisons :
D'abord parce que l'environnement de l'application n'est pas le même pendant les tests et au runtime, puisque dans un cas on est dans COM+, dans une transaction et dans l'autre pas forcément, et même si c'est le cas, il peut y avoir des effets de bord, soyez vigilants.
Ensuite, on a couplé dans un seul test plusieurs appels de méthodes. En théorie c'est mal, car les tests ne sont plus vraiment unitaires. J'ai tendance à négliger ce problème puisque au final les données seront de toutes manières écrites et lues par ma couche d'accès aux données. Tester les 2 en même temps ne me parait pas si hérétique que ça.
Et bien entendu, si la solution de stockage des données ne supporte pas les transactions COM+ ça ne fonctionnera pas.
Et vous quelle solution utilisez-vous pour tester vos couches d'accès aux données ? Bien évidemment si vous ne faites pas de tests unitaires la question ne se pose pas...
...mais c'est très mal !
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :