Unité De Test De Bases De Données
L'été dernier, je développais une base ASP.NET/SQL serveur CRUD app, et les tests unitaires était l'une des exigences. J'ai eu des problèmes quand j'ai essayé de tester la base de données. À ma connaissance, les tests unitaires devraient être:
- apatrides
- indépendants les uns des autres
- reproductible avec les mêmes résultats, c'est-à-dire aucun changement persistant
Ces exigences semblent être en contradiction les unes avec les autres lors du développement d'une base de données. Par exemple, je ne peux pas testez Insert () sans vous assurer que les lignes à insérer ne sont pas encore là, donc je dois d'abord appeler Delete (). Mais, s'ils ne sont pas déjà là? Ensuite, je devrais d'abord appeler la fonction Exists ().
Ma solution éventuelle impliquait de très grandes fonctions d'installation (Beurk!) et un cas de test vide qui s'exécuterait en premier et indiquerait que l'installation s'est déroulée sans problèmes. Cela sacrifie l'indépendance des tests tout en maintenant leur apatridie.
Autre la solution que j'ai trouvée est d'envelopper les appels de fonction dans une transaction qui peut être facilement annulée, commeXtunit de Roy Osherove. Ce travail, mais il implique une autre bibliothèque, une autre dépendance, et il semble un peu trop lourd d'une solution pour le problème à portée de main.
Alors, qu'a fait la communauté SO face à cette situation?
Tgmdbm a dit:
Vous utilisez généralement votre favori cadre de test unitaire automatisé pour effectuer les tests d'intégration, qui est pourquoi certaines personnes se confondent, mais ils ne suivez pas les mêmes règles. Vous êtes autorisé à impliquer le béton mise en œuvre de plusieurs de vos classes (parce qu'ils ont été testés à l'unité). Vous testez comment votre béton les classes interagissent les unes avec les autres et avec la base de données.
Donc, si je lis ceci correctement, il n'y a vraiment aucun moyen deeffectivement tester une couche d'accès aux données. Ou, serait un "test unitaire" d'un accès aux données La couche implique de tester, par exemple, les commandes SQL / générées par les classes, indépendamment de l'interaction réelle avec la base de données?
9 réponses
Il N'y a pas de véritable moyen de tester une base de données à l'unité autre que d'affirmer que les tables existent, contiennent les colonnes attendues et ont les contraintes appropriées. Mais cela ne vaut généralement pas la peine d'être fait.
Vous ne testez généralement pas unit La base de données. Vous impliquez généralement la base de données dans les tests integration .
Vous utilisez généralement votre framework de test unitaire automatisé préféré pour effectuer des tests d'intégration, ce qui explique pourquoi certaines personnes sont confuses, mais ce n'est pas le cas suivre les mêmes règles. Vous êtes autorisé à impliquer la mise en œuvre concrète de plusieurs de vos classes (parce qu'elles ont été testées à l'unité). Vous testez la façon dont vos classes concrètes interagissent les unes avec les autres et avec la base de données.
Vous pouvez utiliser cet outil pour exporter l'état d'une base de données à un moment donné, puis lorsque vous effectuez des tests unitaires, elle peut être automatiquement ramenée à son état précédent au début des tests. Nous l'utilisons assez souvent là où je travaille.
La solution habituelle aux dépendances externes dans les tests unitaires consiste à utiliser des objets fictifs - c'est-à-dire des bibliothèques qui imitent le comportement des vrais contre lesquels vous testez. Ce n'est pas toujours simple, et nécessite parfois un peu d'ingéniosité, mais il existe plusieurs bonnes bibliothèques fictives (freeware) pour.Net si vous ne voulez pas "rouler les vôtres". Deux viennent à l'esprit immédiatement:
Rhino se moque est celui qui a un assez bon réputation.
NMock est un autre.
Il y a aussi beaucoup de bibliothèques fictives commerciales disponibles. Une partie de l'écriture de bons tests unitaires consiste en fait à concevoir votre code pour eux - par exemple, en utilisant des interfaces où cela a du sens, de sorte que vous pouvez "simuler" un objet dépendant en implantant une version" fausse " de son interface qui se comporte néanmoins de manière prévisible, à des fins de test.
Dans les mocks de base de données, cela signifie "se moquer" de votre propre couche D'accès à la base de données avec objets qui renvoient des objets de table, de ligne ou d'ensemble de données composés pour vos tests unitaires.
Là où je travaille, nous fabriquons généralement nos propres libs fictifs à partir de zéro, mais cela ne signifie pas que vous devez le faire.
Ouais, vous devriez refactoriser votre code pour accéder aux référentiels et aux Services qui accèdent à la base de données et vous pouvez ensuite simuler ou stub ces objets afin que l'objet testé ne touche jamais la base de données. C'est beaucoup plus rapide que de stocker l'état de la base de données et de la Réinitialiser après chaque test!
Je recommande fortement Moq comme cadre moqueur. J'ai utilisé Rhino Mocks et NMock. Moq était si simple et résolu tous les problèmes que j'avais avec les autres cadres.
J'ai eu la même question et je suis arrivé aux mêmes conclusions de base que les autres répondeurs ici: ne vous embêtez pas à tester l'Unité de la couche de communication db réelle, mais si vous voulez tester l'Unité de vos fonctions de modèle (pour s'assurer qu'elles tirent correctement les données, les formatent correctement, etc.), utilisez une sorte de source de données factices et de tests de configuration pour vérifier les données récupérées.
Moi aussi, je trouve que la définition des tests unitaires est mauvaise pour beaucoup de développement web activité. Mais cette page décrit des modèles de tests unitaires plus "avancés" et peut aider à inspirer des idées pour appliquer des tests unitaires dans diverses situations:
J'ai expliqué une technique que j'ai utilisée pour cette situation même ici .
L'idée de base est d'exercer chaque méthode dans votre DAL-affirmer vos résultats - et lorsque chaque test est terminé, rollback afin que votre base de données soit propre (pas de données indésirables/test).
Le seul problème que vous pourriez ne pas trouver "génial" est que je fais généralement un test CRUD entier (pas pur du point de vue des tests unitaires) mais ce test d'intégration vous permet de voir votre code de mappage CRUD + en action. De cette façon, si elle se casse, vous saurez avant de lancer l'application (me sauve une tonne de travail quand j'essaie d'aller vite)
Ce que vous devez faire est d'exécuter vos tests à partir d'une copie de la base de données que vous générez à partir d'un script. Vous pouvez exécuter vos tests, puis analyser les données pour vous assurer qu'elles ont exactement ce qu'elles devraient après l'exécution de vos tests. Ensuite, vous supprimez simplement la base de données, car c'est un jetable. Tout cela peut être automatisé et peut être considéré comme une action atomique.
Tester la couche de données et la base de données ensemble laisse peu de surprises pour plus tard dans le
projet. Mais tester contre la base de données a ses problèmes, le principal étant que
vous testez contre l'état partagé par de nombreux tests. Si vous insérez une ligne dans la base de données
dans un test, le test suivant peut également voir cette ligne.
Ce dont vous avez besoin est un moyen de revenir en arrière les modifications que vous apportez à la base de données.
La classeTransactionScope est assez intelligente pour gérer très compliqué transaction,
ainsi que les transactions imbriquées où votre code sous test appelle des validations par lui même
des transactions locales.
Voici un simple morceau de code qui montre à quel point il est facile d'ajouter une capacité de restauration à
vos tests:
[TestFixture]
public class TrannsactionScopeTests
{
private TransactionScope trans = null;
[SetUp]
public void SetUp()
{
trans = new TransactionScope(TransactionScopeOption.Required);
}
[TearDown]
public void TearDown()
{
trans.Dispose();
}
[Test]
public void TestServicedSameTransaction()
{
MySimpleClass c = new MySimpleClass();
long id = c.InsertCategoryStandard("whatever");
long id2 = c.InsertCategoryStandard("whatever");
Console.WriteLine("Got id of " + id);
Console.WriteLine("Got id of " + id2);
Assert.AreNotEqual(id, id2);
}
}
Si vous utilisez LINQ to SQL comme ORM, vous pouvez générer la base de données à la volée (à condition que vous ayez suffisamment d'accès à partir du compte utilisé pour les tests unitaires). Voir http://www.aaron-powell.com/blog.aspx?id=1125