Comment tester un object avec des requêtes de firebase database

J’ai entendu dire que les tests unitaires étaient “tout à fait géniaux”, “vraiment cool” et “toutes sortes de bonnes choses”, mais 70% ou plus de mes fichiers impliquent un access à la firebase database écrire un test unitaire pour ces fichiers.

J’utilise PHP et Python mais je pense que c’est une question qui s’applique à la plupart des langues utilisant l’access à la firebase database.

Je suggère de railler vos appels à la firebase database. Les mocks sont essentiellement des objects qui ressemblent à l’object sur lequel vous essayez d’appeler une méthode, en ce sens qu’ils ont les mêmes propriétés, méthodes, etc. disponibles pour l’appelant. Mais au lieu d’exécuter les actions programmées lorsqu’une méthode particulière est appelée, elle saute complètement et renvoie simplement un résultat. Ce résultat est généralement défini par vous à l’avance.

Afin de configurer vos objects pour vous moquer, vous devez probablement 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 unitaire, vous créez une maquette de FooDataProvider, qui vous permet d’appeler la méthode GetAllFoos sans avoir à bash la firebase database.

 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 moqueur commun, en un mot. Bien entendu, vous voudrez probablement toujours tester vos appels à la firebase database, pour lesquels vous devrez accéder à la firebase database.

Idéalement, vos objects devraient être des ignorants persistants. Par exemple, vous devriez avoir une “couche d’access aux données” sur laquelle vous feriez des requêtes et qui renverrait des objects. De cette façon, vous pouvez laisser cette partie de vos tests unitaires ou les tester séparément.

Si vos objects sont étroitement liés à votre couche de données, il est difficile d’effectuer des tests unitaires appropriés. la première partie du test unitaire est “unit”. Toutes les unités doivent 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 objects vivent dans le modèle de domaine principal et sont accessibles à partir de ma couche d’application. La couche d’application communique avec la couche de données et la couche de modèle de domaine.

La couche d’application est parfois appelée “couche métier”.

Si vous utilisez PHP, créez un ensemble spécifique de classes pour l’access aux données UNIQUEMENT . Assurez-vous que vos objects n’ont aucune idée de la manière dont ils sont conservés et connectez les deux dans vos classes d’applications.

Une autre option consisterait à utiliser des moqueries / des tronçons.

Le moyen le plus simple de tester un object avec un access à la firebase database consiste à utiliser des scopes 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 rétablira l’état de la firebase database, essentiellement comme une annulation de transaction, afin que vous puissiez exécuter le test autant de fois que vous le souhaitez sans effets secondaires. Nous avons utilisé cette approche avec succès dans les grands projets. Notre build prend un peu de temps à fonctionner (15 minutes), mais ce n’est pas horrible d’avoir 1800 tests unitaires. En outre, si le temps de compilation est un problème, vous pouvez modifier le processus de génération pour qu’il comporte plusieurs versions, une pour la construction de src, une autre pour les tests unitaires, l’parsing de code, le packaging, etc.

Vous devez simuler l’access à la firebase database si vous souhaitez tester vos classes à l’unité. Après tout, vous ne voulez pas tester la firebase database dans un test unitaire. Ce serait un test d’intégration.

Abstenez les appels, puis insérez une maquette qui renvoie simplement 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, cependant …

Je peux peut-être vous donner un aperçu de notre expérience lorsque nous avons commencé à examiner les tests unitaires de notre processus de niveau intermédiaire, qui comprenait une multitude d’opérations SQL de type «logique métier».

Nous avons d’abord créé une couche d’abstraction qui nous permettait de “placer” toute connexion de firebase database raisonnable (dans notre cas, nous prenions simplement en charge une seule connexion de type ODBC).

Une fois que cela était en place, nous avons pu faire quelque chose comme ça dans notre code (nous travaillons en C ++, mais je suis sûr que vous avez l’idée):

GetDatabase (). ExecuteSQL (“INSERT INTO foo (blah, blah)”)

Lors de l’exécution normale, GetDatabase () renverrait un object qui alimenterait tous nos sql (y compris les requêtes), via ODBC, directement dans la firebase database.

Nous avons ensuite commencé à examiner les bases de données en mémoire – le meilleur par excellence semble être SQLite. ( http://www.sqlite.org/index.html ). Il est remarquablement simple à configurer et à utiliser, et nous a permis de sous-classer et de remplacer GetDatabase () pour transférer sql vers une firebase database en mémoire créée et détruite pour chaque test effectué.

Nous en sums encore aux premiers stades, mais nous paraissons en bonne forme jusqu’à présent, mais nous devons nous assurer de créer des tables requirejses et de les remplir avec des données de test. Cependant, nous avons quelque peu réduit la charge de travail en créant un ensemble générique de fonctions d’aide qui peuvent faire beaucoup de tout cela pour nous.

Globalement, cela a énormément aidé avec notre processus TDD, car faire des changements qui semblent anodins pour réparer certains bogues peut avoir des effets assez étranges sur d’autres zones (difficiles à détecter) de votre système – en raison de la nature même de sql / databases.

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

J’espère que cela t’aides.

Le livre xUnit Test Patterns décrit quelques manières de gérer le code de test d’unité qui frappe une firebase database. Je suis d’accord avec les autres personnes qui disent que vous ne voulez pas faire ça parce que c’est lent, mais vous devez le faire parfois, à mon avis. Il peut être utile de simuler la connexion à la firebase database pour tester des éléments de niveau supérieur, mais consultez ce livre pour obtenir des suggestions sur ce que vous pouvez faire pour interagir avec la firebase database réelle.

Options que vous avez:

  • Ecrivez un script qui effacera la firebase database avant de lancer les tests unitaires, puis remplissez db avec un dataset prédéfini et exécutez les tests. Vous pouvez également le faire avant chaque test – ce sera lent, mais moins sujet aux erreurs.
  • Injecter la firebase database. (Exemple dans pseudo-Java, mais s’applique à tous les langages OO)

     classer la firebase database {
      public query de requête (requête de chaîne) {... vraie firebase database ici ...}
     } 

    la classe MockDatabase étend la firebase database {
    requête de résultat publique (requête de chaîne) {
    retourner “résultat simulé”;
    }
    }

    class ObjectThatUsesDB {
    ObjectThatUsesDB public (firebase database db) {
    this.database = db;
    }
    }
    maintenant, en production, vous utilisez une firebase database normale et pour tous les tests, il vous suffit d’injecter la firebase database simulée que vous pouvez créer ad hoc.

  • N’utilisez pas du tout de firebase database dans la plupart des codes (ce qui est une mauvaise pratique de toute façon). Créez un object “firebase database” qui, au lieu de retourner avec les résultats, renverra des objects normaux (c’est-à-dire renverra un User au lieu d’un tuple {name: "marcin", password: "blah"} ) écrire un gros test qui dépend d’une firebase database qui vérifie que cette conversion fonctionne correctement.

Bien sûr, ces approches ne sont pas incompatibles et vous pouvez les combiner selon vos besoins.

J’essaie généralement de briser mes tests entre tester les objects (et ORM, le cas échéant) et tester la firebase database. Je teste le côté object en moquant les appels d’access aux données alors que je teste le côté de la firebase database en testant les interactions d’object avec la firebase database, ce qui est, selon mon expérience, assez limité.

J’avais l’habitude d’être frustré par les tests d’écriture jusqu’à ce que je commence à me moquer de la partie d’access aux données, donc je n’ai pas eu à créer une firebase database de test ou à générer des données de test à la volée. En moquant les données, vous pouvez les générer au moment de l’exécution et vous assurer que vos objects fonctionnent correctement avec des entrées connues.

Je ne l’ai jamais fait en PHP et je n’ai jamais utilisé Python, mais ce que vous voulez faire, c’est simuler les appels à la firebase database. Pour ce faire, vous pouvez implémenter un IoC que ce soit un outil tiers ou vous le gérez vous-même, puis vous pouvez implémenter une version fictive de l’appelant de la firebase database, qui vous permet de contrôler le résultat de cet appel fictif.

Une forme simple de IoC peut être effectuée simplement en codant les interfaces. Cela nécessite une sorte d’orientation de l’object dans votre code pour qu’elle ne s’applique pas à ce que vous faites (je dis cela puisque tout ce que je dois faire est de mentionner PHP et Python)

J’espère que c’est utile, si rien d’autre vous avez des termes à rechercher maintenant.

Je suis d’accord avec le premier post-firebase database, l’access doit être éliminé dans une couche DAO qui implémente une interface. Ensuite, vous pouvez tester votre logique contre une implémentation de stub de la couche DAO.

Vous pouvez utiliser des frameworks moqueurs pour extraire le moteur de firebase database. Je ne sais pas si PHP / Python en a quelques-uns, mais pour les langages typés (C #, Java, etc.), il y a beaucoup de choix

Cela dépend également de la façon dont vous avez conçu ces codes d’access à la firebase database, car certains modèles sont plus faciles à tester que d’autres, comme les articles précédents l’ont mentionné.

Le test unitaire de votre access à la firebase database est assez facile si votre projet a une grande cohésion et un couplage lâche. De cette façon, vous ne pouvez tester que ce que fait chaque classe sans avoir à tout tester en même temps.

Par exemple, si vous testez votre classe d’interface utilisateur, les tests que vous écrivez doivent uniquement vérifier que la logique de l’interface utilisateur a fonctionné comme prévu, et non pas la logique métier ou l’action de firebase database derrière cette fonction.

Si vous souhaitez tester l’access à la firebase database, vous devrez effectuer un test d’intégration, car vous serez dépendant de la stack réseau et de votre serveur de firebase database, mais vous pourrez vérifier que votre code SQL fait ce que vous lui avez demandé. faire.

Pour moi, le pouvoir caché des tests unitaires est que cela m’oblige à concevoir mes applications de manière beaucoup plus efficace que sans eux. C’est parce que cela m’a vraiment aidé à rompre avec la mentalité “cette fonction devrait tout faire”.

Désolé je n’ai aucun exemple de code spécifique pour PHP / Python, mais si vous voulez voir un exemple .NET, j’ai un article décrivant une technique que j’ai utilisée pour faire les mêmes tests.

La configuration des données de test pour les tests unitaires peut constituer un défi.

En ce qui concerne Java, si vous utilisez les API Spring pour les tests unitaires, vous pouvez contrôler les transactions au niveau de l’unité. En d’autres termes, vous pouvez exécuter des tests unitaires impliquant des mises à jour / insertions / suppressions de bases de données et la restauration des modifications. A la fin de l’exécution, vous laissez tout dans la firebase database avant de commencer l’exécution. Pour moi, c’est aussi bon que possible.