Question

L'été dernier, je développais une application CRUD de base ASP.NET/SQL Server, et les tests unitaires étaient l'une des exigences.J'ai rencontré des problèmes lorsque j'ai essayé de tester la base de données.À ma connaissance, les tests unitaires devraient être :

  • apatride
  • indépendants les uns des autres
  • reproductible avec les mêmes résultats, c'est-à-direaucun 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 tester Insert() sans m'assurer que les lignes à insérer ne sont pas encore là, je dois donc d'abord appeler Delete().Mais que se passe-t-il s’ils ne sont pas déjà là ?Ensuite, je devrais d’abord appeler la fonction Exists().

Ma solution finale impliquait de très grandes fonctions d'installation (beurk !) et un scénario de test vide qui s'exécuterait en premier et indiquerait que l'installation s'est déroulée sans problème.C'est sacrifier l'indépendance des tests tout en maintenant leur apatridie.

Une autre solution que j'ai trouvée consiste à envelopper les appels de fonction dans une transaction qui peut être facilement annulée, comme XtUnit de Roy Osherove.Cela fonctionne, mais cela implique une autre bibliothèque, une autre dépendance, et cela semble une solution un peu trop lourde pour le problème en question.

Alors, qu’a fait la communauté SO face à cette situation ?


tgmdbm a dit :

Vous utilisez généralement votre cadre de test d'unité automatisé préféré pour effectuer des tests d'intégration, c'est pourquoi certaines personnes sont confuses, mais elles ne respectent pas les mêmes règles.Vous êtes autorisé à impliquer la mise en œuvre du béton de plusieurs de vos classes (car elles ont été testées unitaires).Vous testez Comment vos classes en béton interagissent entre elles et avec la base de données.

Donc si j'ai bien lu, il n'y a vraiment aucun moyen de effectivement testez unitairement une couche d’accès aux données.Ou, un « test unitaire » d'une couche d'accès aux données impliquerait-il de tester, par exemple, le SQL/les commandes générées par les classes, indépendamment de l'interaction réelle avec la base de données ?

Était-ce utile?

La solution

Il n'existe aucun moyen réel de tester unitairement une base de données autre que d'affirmer que les tables existent, contiennent les colonnes attendues et ont les contraintes appropriées.Mais cela n’en vaut généralement pas la peine.

En général, vous ne le faites pas unité tester la base de données.Vous impliquez généralement la base de données dans l'intégration essais.

Vous utilisez généralement votre framework de tests unitaires automatisés préféré pour effectuer des tests d'intégration, c'est pourquoi certaines personnes sont confuses, mais elles ne suivent pas les mêmes règles.Vous êtes autorisé à impliquer la mise en œuvre concrète de plusieurs de vos classes (car elles ont été testées unitairement).Vous testez la façon dont vos classes concrètes interagissent entre elles et avec la base de données.

Autres conseils

UnitéDB

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 restauré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 objets réels sur lesquels vous testez.Ce n'est pas toujours simple et nécessite parfois une certaine ingéniosité, mais il existe plusieurs bonnes bibliothèques fictives (gratuites) pour .Net si vous ne voulez pas "créer la vôtre".Deux me viennent immédiatement à l’esprit :

Des moqueries de rhinocéros en est un qui a une assez bonne réputation.

NMock en est une autre.

Il existe également de nombreuses bibliothèques commerciales fictives.Une partie de l'écriture de bons tests unitaires consiste en fait à concevoir votre code pour eux - par exemple, en utilisant des interfaces là où cela a du sens, afin que vous puissiez "vous moquer" d'un objet dépendant en implémentant une "fausse" version de son interface qui se comporte néanmoins de manière manière prévisible, à des fins de test.

Dans les simulations de bases de données, cela signifie "se moquer" de votre propre couche d'accès à la base de données avec des objets qui renvoient des objets de table, de ligne ou d'ensemble de données constitués à traiter par vos tests unitaires.

Là où je travaille, nous créons généralement nos propres bibliothèques fictives à partir de zéro, mais cela ne signifie pas que vous devez le faire.

Oui, vous devez 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 vous moquer ou supprimer 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ésolvait tous les problèmes que j'avais avec les autres frameworks.

J'ai eu la même question et je suis arrivé aux mêmes conclusions fondamentales que les autres répondants ici :Ne vous embêtez pas à tester unitairement la couche de communication de la base de données réelle, mais si vous souhaitez tester unitairement vos fonctions de modèle (pour vous assurer qu'elles extraient correctement les données, les formatent correctement, etc.), utilisez une sorte de source de données factice et des tests de configuration. pour vérifier les données récupérées.

Moi aussi, je trouve que la définition simple des tests unitaires ne convient pas à de nombreuses activités de développement Web.Mais cette page décrit des modèles de tests unitaires plus « avancés » et peut aider à inspirer quelques idées pour appliquer les tests unitaires dans diverses situations :

Modèles de tests unitaires

J'ai expliqué une technique que j'utilise pour cette situation précise ici.

L'idée de base est d'exercer chaque méthode de votre DAL - d'affirmer vos résultats - et lorsque chaque test est terminé, de revenir en arrière afin que votre base de données soit propre (pas de données indésirables/de test).

Le seul problème que vous ne trouverez peut-être pas « génial » est que je fais généralement un test CRUD complet (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 cela tombe en panne, vous le saurez avant de lancer l'application (cela me fait gagner une tonne de travail lorsque j'essaie d'aller vite)

Ce que vous devez faire est d'exécuter vos tests à partir d'une copie vierge 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 contiennent 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 produit jetable.Tout cela peut être automatisé et peut être considéré comme une action atomique.

Le test de la couche de données et de la base de données laisse ensemble quelques surprises pour plus tard dans le projet.Mais le test 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, c'est d'un moyen d'annuler les modifications que vous apportez à la base de données.
Le Portée des transactions La classe est suffisamment intelligente pour gérer des transactions très compliquées, ainsi que des transactions imbriquées où votre code sous les appels de test s'engage sur sa propre transaction locale.Voici un simple morceau de code qui montre à quel point il est facile d'ajouter la capacité de retour à 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 disposiez d'un accès suffisant depuis le compte utilisé pour les tests unitaires).Voir http://www.aaron-powell.com/blog.aspx?id=1125

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top