Comment tester un objet avec des requêtes de base de données

j'ai entendu que le test de l'unité est" totalement génial"," vraiment cool "et" toutes sortes de bonnes choses " mais 70% ou plus de mes fichiers impliquent l'accès à la base de données (certains lire et certains écrire) et je ne sais pas comment écrire un test de l'unité pour ces fichiers.

J'utilise PHP et Python mais je pense que c'est une question qui s'applique à la plupart/toutes les langues qui utilisent l'accès à la base de données.

133
demandé sur Teifion 2008-08-27 21:46:02

13 réponses

je vous suggère de vous moquer de vos appels à la base de données. Les moqueries sont essentiellement des objets qui ressemblent à l'objet sur lequel vous essayez d'appeler une méthode, dans le sens où ils ont les mêmes propriétés, méthodes, etc. disponible à l'appelant. Mais au lieu d'effectuer ce qu'ils sont programmés pour faire lorsqu'une méthode est appelée, il ignore que, globalement, et retourne un résultat. Ce résultat est généralement défini par vous à l'avance.

afin de configurez vos objets pour vous moquer, vous avez probablement besoin d'utiliser une sorte d'inversion du modèle d'injection de contrôle/ dépendance, comme dans le pseudo-code suivant:

class Bar
{
    private FooDataProvider _dataProvider;

    public instantiate(FooDataProvider dataProvider) {
        _dataProvider = dataProvider;
    }

    public getAllFoos() {
        // instead of calling Foo.GetAll() here, we are introducing an extra layer of abstraction
        return _dataProvider.GetAllFoos();
    }
}

class FooDataProvider
{
    public Foo[] GetAllFoos() {
        return Foo.GetAll();
    }
}

maintenant dans votre test d'unité, vous créez un mock of FooDataProvider, qui vous permet d'appeler la méthode GetAllFoos sans avoir à réellement frapper la base de données.

class BarTests
{
    public TestGetAllFoos() {
        // here we set up our mock FooDataProvider
        mockRepository = MockingFramework.new()
        mockFooDataProvider = mockRepository.CreateMockOfType(FooDataProvider);

        // create a new array of Foo objects
        testFooArray = new Foo[] {Foo.new(), Foo.new(), Foo.new()}

        // the next statement will cause testFooArray to be returned every time we call FooDAtaProvider.GetAllFoos,
        // instead of calling to the database and returning whatever is in there
        // ExpectCallTo and Returns are methods provided by our imaginary mocking framework
        ExpectCallTo(mockFooDataProvider.GetAllFoos).Returns(testFooArray)

        // now begins our actual unit test
        testBar = new Bar(mockFooDataProvider)
        baz = testBar.GetAllFoos()

        // baz should now equal the testFooArray object we created earlier
        Assert.AreEqual(3, baz.length)
    }
}

un scénario de moquerie courant,en un mot. Bien sûr, vous voudrez probablement encore unité tester votre les appels réels de base de données aussi, pour lequel vous aurez besoin de frapper la base de données.

71
répondu Doug R 2018-07-15 19:44:02

idéalement, vos objets devraient être persistants ignorants. Par exemple, vous devriez avoir une "couche d'accès aux données", à laquelle vous feriez des requêtes, qui retournerait des objets. De cette façon, vous pouvez laisser cette partie en dehors de vos tests unitaires, ou les tester en isolation.

si vos objets sont étroitement couplés à votre couche de données, il est difficile de faire des tests unitaires appropriés. la première partie du test unitaire est "unité". Toutes les unités devraient pouvoir être testées isolément.

dans mes projets c#, J'utilise NHibernate avec une couche de données complètement séparée. Mes objets vivent dans le modèle de domaine core et sont accessibles depuis ma couche application. La couche application s'adresse à la fois à la couche data et à la couche domain model.

de La couche application est aussi parfois appelée "Couche".

si vous utilisez PHP, créez un ensemble spécifique de classes pour l'accès aux données seulement . Assurez-vous que votre les objets n'ont aucune idée de la façon dont ils sont persistants et de relier les deux dans vos classes d'application.

une autre option serait d'utiliser moqueries/talons.

23
répondu Sean Chambers 2008-08-27 17:49:24

la façon la plus facile de tester un objet avec un accès à une base de données est d'utiliser des portées de transaction.

par exemple:

    [Test]
    [ExpectedException(typeof(NotFoundException))]
    public void DeleteAttendee() {

        using(TransactionScope scope = new TransactionScope()) {
            Attendee anAttendee = Attendee.Get(3);
            anAttendee.Delete();
            anAttendee.Save();

            //Try reloading. Instance should have been deleted.
            Attendee deletedAttendee = Attendee.Get(3);
        }
    }

cela va revenir en arrière l'état de la base de données, essentiellement comme un rollback de transaction de sorte que vous pouvez exécuter le test autant de fois que vous voulez sans effets secondaires. Nous avons utilisé cette approche avec succès dans de grands projets. Notre construction prend un peu de temps à courir (15 minutes), mais il n'est pas terrible d'avoir 1800 les tests unitaires. En outre, si le temps de compilation est un problème, vous pouvez changer le processus de compilation pour avoir plusieurs compilations, une pour construire src, une autre qui démarre ensuite qui gère les tests unitaires, l'analyse de code, le packaging, etc...

11
répondu BZ. 2008-08-27 18:14:54

vous devriez vous moquer de l'accès à la base de données si vous voulez tester en unité vos classes. Après tout, vous ne voulez pas tester la base de données dans un test unitaire. Que serait un test d'intégration.

supprime les appels et insère un mock qui retourne juste les données attendues. Si vos classes ne font pas plus que d'exécuter des requêtes, cela ne vaut peut-être même pas la peine de les tester...

6
répondu Martin Klinke 2008-08-27 17:49:28

je peux peut-être vous donner un avant-goût de notre expérience lorsque nous avons commencé à examiner à l'unité tester notre processus de niveau intermédiaire qui comprenait une tonne d'opérations sql" logique d'affaires".

nous avons d'abord créé une couche d'abstraction qui nous a permis de" saisir " toute connexion raisonnable à une base de données (dans notre cas, nous avons simplement supporté une connexion de type ODBC).

une fois que cela a été en place, Nous avons été en mesure de faire quelque chose comme cela dans notre code (nous travaillons en C++, mais je suis sûr que vous voyez l'idée):

GetDatabase().ExecuteSQL ("INSERT INTO foo (blah, blah)")

à l'heure d'exécution normale, GetDatabase() retournerait un objet qui alimente tous nos sql (y compris les requêtes), via ODBC directement dans la base de données.

nous avons alors commencé à regarder des bases de données en mémoire-Le Meilleur par un long chemin semble être SQLite. ( http://www.sqlite.org/index.html ). Il est remarquablement simple de configurer et utiliser, et nous a permis de sous-classe et de surcharger GetDatabase() pour transférer sql vers une base de données en mémoire qui a été créée et détruite pour chaque test effectué.

nous en sommes encore aux premières étapes, mais cela semble bien jusqu'à présent, cependant nous devons nous assurer que nous créons toutes les tables qui sont nécessaires et les peupler avec des données de test - cependant nous avons réduit la charge de travail un peu ici en créant un ensemble générique de fonctions d'aide qui peuvent faire beaucoup de tout cela pour US.

dans l'ensemble, il a énormément aidé avec notre processus TDD, car faire ce qui semble être des changements assez inoffensifs pour corriger certains bogues peut avoir des effets assez étranges sur d'autres (difficiles à détecter) zones de votre système - en raison de la nature même de sql/bases de données.

évidemment, nos expériences se sont centrées sur un environnement de développement C++, mais je suis sûr que vous pourriez peut-être obtenir quelque chose de similaire sous PHP/Python.

Espérons que cette aide.

6
répondu Alan 2008-09-06 23:04:23

Le livre xUnit des Modèles de Test , décrit certains des façons de gérer l'unité de test de code qui accède à une base de données. Je suis d'accord avec les autres personnes qui disent que tu ne veux pas faire ça parce que c'est lent, mais tu dois le faire de temps en temps, IMO. Se moquer de la connexion db pour tester des choses de haut niveau est une bonne idée, mais consultez ce livre pour des suggestions sur les choses que vous pouvez faire pour interagir avec la base de données réelle.

4
répondu Chris Farmer 2008-08-27 17:59:51

Options que vous avez:

  • Ecrivez un script qui effacera la base de données avant de lancer les tests unitaires, puis peuplez db avec un ensemble prédéfini de données et lancez les tests. Vous pouvez également le faire avant chaque test – ce sera lent, mais moins susceptible d'erreur.
  • injecte la base de données. (Exemple en pseudo-Java, mais s'applique à toutes les langues OO)

    class Database {
     public Result query(String query) {... real db here ...}
    }
    
    

    class MockDatabase extends Database { public Result query(String query) { return "mock result"; } }

    class ObjectThatUsesDB { public ObjectThatUsesDB(Database db) { this.database = db; } }

    maintenant dans la production vous utilisez la base de données normale et pour tous les tests vous il suffit d'injecter la base de données simulée que vous pouvez créer ad hoc.
  • N'utilisez pas du tout DB dans la plupart des codes (c'est une mauvaise pratique de toute façon). Créez un objet" database "Qui au lieu de retourner avec les résultats retournera des objets normaux (i.e. retournera User au lieu d'un tuple {name: "marcin", password: "blah"} ) écrivez tous vos tests avec des objets construits ad hoc réels et écrivez un grand test qui dépend d'une base de données qui s'assure que cette conversion fonctionne bien.

bien sûr, ces approches ne sont pas mutuellement exclusives et vous pouvez les mélanger et les assortir comme vous avez besoin.

4
répondu Marcin 2008-08-27 18:11:37

j'essaie habituellement de briser mes tests entre tester les objets (et ORM, le cas échéant) et tester le db. Je teste le côté objet des choses en moquant les appels d'accès aux données tandis que je teste le côté db des choses en testant les interactions objet avec le db qui est, selon mon expérience, généralement assez limité.

j'avais l'habitude d'être frustré par l'écriture de tests unitaires jusqu'à ce que je commence à me moquer de la partie d'accès aux données de sorte que je n'ai pas eu à créer un test db ou générer des données de test à la volée. En vous moquant des données, vous pouvez les générer au moment de l'exécution et vous assurer que vos objets fonctionnent correctement avec des entrées connues.

2
répondu akmad 2008-08-27 17:51:40

Je n'ai jamais fait cela en PHP et je n'ai jamais utilisé Python, mais ce que vous voulez faire est de mock out les appels à la base de données. Pour ce faire, vous pouvez mettre en œuvre certains CIO que ce soit un outil tiers ou que vous le gériez vous-même, alors vous pouvez mettre en œuvre une version simulée de l'appelant de base de données qui est où vous contrôlerez le résultat de cet appel faux.

une forme simple de recherche des causes peut être effectuée simplement en codant aux Interfaces. Cela nécessite une sorte de l'orientation de l'objet est en cours dans votre code de sorte qu'elle ne peut pas s'appliquer à ce que vous faites (je dis que puisque tout ce que j'ai à faire est votre mention de PHP et Python)

J'espère que c'est utile, si vous n'avez rien d'autre à chercher.

2
répondu codeLes 2008-08-27 17:55:53

je suis d'accord avec le premier post - accès de base de données doit être enlevée dans une couche DAO qui implémente une interface. Ensuite, vous pouvez tester votre logique par rapport à une implémentation tronquée de la couche DAO.

2
répondu Chris Marasti-Georg 2008-08-27 17:56:19

vous pouvez utiliser mocking frameworks pour extraire le moteur de la base de données. Je ne sais pas si PHP/Python en a eu, mais pour les langages dactylographiés (C#, Java etc.) Il ya beaucoup de choix

cela dépend aussi de la façon dont vous avez conçu ce code d'accès à la base de données, parce que certains design sont plus faciles à tester en unité que d'autres comme les messages précédents ont mentionné.

2
répondu chakrit 2008-08-27 17:59:02

Unité tester votre accès à la base de données est assez facile si votre projet a une grande cohésion et le couplage lâche tout au long. De cette façon, vous pouvez tester seulement les choses que chaque classe particulière fait sans avoir à tout tester à la fois.

par exemple, si votre unité teste votre classe d'interface utilisateur, les tests que vous écrivez ne devraient essayer de vérifier que la logique à l'intérieur de L'UI a fonctionné comme prévu, et non la logique d'affaires ou l'action de base de données derrière cette fonction.

si vous voulez tester l'accès réel à la base de données, vous finirez en fait avec plus d'un test d'intégration, parce que vous serez dépendant de la pile réseau et de votre serveur de base de données, mais vous pouvez vérifier que votre code SQL fait ce que vous lui avez demandé de faire.

le pouvoir caché des tests unitaires pour moi personnellement a été qu'il me force à concevoir mes applications d'une manière bien meilleure que je ne le ferais sans eux. C'est parce qu'il m'a vraiment aidé à rompre de la "cette fonction doit tout faire" mentalité.

Désolé je n'ai pas d'exemples de code spécifiques pour PHP/Python, mais si vous voulez voir un exemple .NET j'ai un post qui décrit une technique que j'ai utilisée pour faire ce même test.

2
répondu Toran Billups 2017-05-23 12:02:42

la mise en place de données d'essai pour les essais unitaires peut être un défi.

quand il s'agit de Java, si vous utilisez des API printemps pour tester les unités, vous pouvez contrôler les transactions au niveau de l'unité. En d'autres termes, vous pouvez exécuter des tests unitaires qui impliquent des mises à jour/inserts/suppressions de bases de données et de faire reculer les modifications. A la fin de l'exécution, vous laissez tout dans la base de données comme avant l'exécution. Pour moi, c'est aussi bon qu'il peut obtenir.

2
répondu Bino Manjasseril 2011-11-18 21:50:14