Données de base de données nécessaires dans les tests d'intégration; créées par des appels D'API ou à l'aide de données importées?
Cette question est plus ou moins agnostique en langage de programmation. Cependant, comme je suis surtout dans Java ces jours-ci, c'est de là que je vais tirer mes exemples. Je pense aussi au cas OOP, donc si vous voulez tester une méthode, vous avez besoin d'une instance de cette classe methods.
A règle de Base pour tests unitaires est-ce qu'ils devraient être autonomes, et cela peut être réalisé en isolant une classe de ses dépendances. Il y a plusieurs façons de le faire et cela dépend si vous injectez vos dépendances en utilisant IoC (dans le monde Java, nous avons Spring, EJB3 et d'autres frameworks/plates-formes qui fournissent des capacités d'injection) et/ou si vous simulez des objets (pour Java, vous avez JMock et EasyMock) pour séparer une classe testée de ses dépendances.
Si nous devons tester des groupes de méthodes dans différentes classes* et voir qu'ils sont bien intégrés, nous écrivons tests d'intégration. Et voici mon question!
- au moins dans les applications web, l'état est souvent conservé dans une base de données. Nous pourrions utiliser les mêmes outils que pour les tests unitaires pour atteindre l'indépendance de la base de données. Mais à mon humble avis, je pense qu'il y a des cas où ne pas utiliser une base de données pour les tests d'intégration se moque trop (mais n'hésitez pas à être en désaccord; ne pas utiliser une base de données du tout, jamais, est aussi une réponse valide car cela rend la question non pertinente).
-
Lorsque vous utilisez une base de données pour les tests d'intégration, comment remplissez-vous cette base de données avec des données? je peux voir deux approches:
- Stockez le contenu de la base de données pour le test d'intégration et chargez-le avant de commencer le test. S'il est stocké sous forme de vidage SQL, un fichier de base de données, XML ou autre chose serait intéressant à savoir.
- créez les structures de base de données nécessaires par des appels D'API. Ces appels sont probablement divisé en plusieurs méthodes dans votre code de test et chacune de ces méthodes peut échouer. Il pourrait être considéré comme votre test d'intégration ayant des dépendances sur d'autres tests.
Comment vous assurez-vous que les données de base de données nécessaires aux tests sont là quand vous en avez besoin? Et pourquoi avez-vous choisi la méthode que vous choisissez?
Veuillez fournir une réponse avec une motivation , car c'est dans la motivation que réside la partie intéressante. Rappelez-vous que juste dire "C'est la meilleure pratique!"n'est-ce pas une motivation réelle, c'est juste une ré-itération de quelque chose que vous avez lu ou entendu de quelqu'un. Pour que cas, veuillez expliquer pourquoi c'est la meilleure pratique.
*j'inclus une méthode appelant d'autres méthodes dans (la même ou d'autres) instances de la même classe dans ma définition de test unitaire, même si cela pourrait techniquement ne pas être correct. N'hésitez pas à me corriger, mais gardons-le comme un problème secondaire.
8 réponses
Je préfère créer les données de test en utilisant des appels API.
Au début du test, vous créez une base de données vide (en mémoire ou la même que celle utilisée en production), exécutez le script d'installation pour l'initialiser, puis créez les données de test utilisées par la base de données. La création des données de test peut être organisée par exemple avec le modèleObject Mother , de sorte que les mêmes données peuvent être réutilisées dans de nombreux tests, éventuellement avec des variations mineures.
Vous voulez avoir la base de données dans un état connu avant chaque test, afin d'avoir des tests sans effets secondaires. Ainsi, lorsqu'un test se termine, vous devez supprimer la base de données de test ou annuler la transaction, de sorte que le test suivant puisse recréer les données de test de la même manière, que les tests précédents aient réussi ou échoué.
La raison pour laquelle j'éviterais d'importer des vidages de base de données (ou similaires), est qu'il couplera les données de test avec le schéma de base de données. Lorsque le schéma de base de données change, vous aussi besoin de changer ou de recréer les données de test, ce qui peut nécessiter un travail manuel.
Si les données de test sont spécifiées dans le code, vous aurez la puissance des outils de refactoring de votre IDE à portée de main. Lorsque vous effectuez une modification qui affecte le schéma de base de données, elle affectera probablement également les appels D'API, de sorte que vous devrez de toute façon refactoriser le code en utilisant L'API. Avec presque le même effort, vous pouvez également refactoriser la création des données de test-surtout si le refactoring peut être automatisé (renommage, introduction de paramètres, etc.). Mais si les tests reposent sur un vidage de base de données, vous devrez refactoriser manuellement le vidage de base de données en plus de refactoriser le code qui utilise L'API.
Une autre chose liée au test d'intégration de la base de données, est de tester que la mise à niveau à partir d'un schéma de base de données précédent fonctionne correctement. Pour cela, vous pouvez lire le livre Refactoring Databases: Evolutionary Database Design ou cet article: http://martinfowler.com/articles/evodb.html
Dans les tests d'intégration, vous devez tester avec une base de données réelle, car vous devez vérifier que votre application peut réellement parler à la base de données. Isoler la base de données en tant que dépendance signifie que vous reportez le test réel de savoir si votre base de données a été déployée correctement, votre schéma est comme prévu et votre application est configurée avec la bonne chaîne de connexion. Vous ne voulez pas trouver de problèmes avec ceux-ci lorsque vous déployez en production.
Vous souhaitez également tester avec les deux données précréées ensembles et ensemble de données vide. Vous devez tester à la fois le chemin où votre application commence avec une base de données vide avec uniquement vos données initiales par défaut et commence à créer et à remplir les données, ainsi qu'avec des ensembles de données bien définis qui ciblent les conditions spécifiques que vous souhaitez tester, comme le stress, les performances,etc.
Aussi, assurez-vous que vous avez la base de données dans un État bien connu avant chaque État. Vous ne voulez pas avoir de dépendances entre vos tests d'intégration.
J'ai utilisé DBUnit pour prendre des instantanés d'enregistrements dans une base de données et les stocker au format XML. Ensuite, mes tests unitaires (nous les avons appelés tests d'intégration quand ils nécessitaient une base de données), peuvent effacer et restaurer à partir du fichier XML au début de chaque test.
Je suis indécis si cela en vaut la peine. Un problème est les dépendances sur d'autres tables. Nous avons laissé les tables de référence statiques seules et avons construit des outils pour détecter et extraire toutes les tables enfants avec les enregistrements demandés. Je lisez la recommandation de quelqu'un pour désactiver toutes les clés étrangères dans votre base de données de test d'intégration. Cela faciliterait la préparation des données, mais vous ne vérifiez plus aucun problème d'intégrité référentielle dans vos tests.
Un autre problème est les changements de schéma de base de données. Nous avons écrit quelques outils qui ajouteraient des valeurs par défaut pour les colonnes qui avaient été ajoutées depuis les instantanés ont été prises.
Évidemment, ces tests étaient beaucoup plus lents que les tests unitaires purs.
Quand vous essayez de tester un code hérité où il est très difficile d'écrire des tests unitaires pour des classes individuelles, cette approche peut en valoir la peine.
Il semble que votre question soit en fait deux questions. Devriez-vous utiliser exclure la base de données de vos tests? Quand pensez-vous d'une base de données, puis comment générer les données dans la base de données?
Si possible, je préfère utiliser une base de données réelle. Fréquemment les requêtes (écrites en SQL, HQL, etc.) dans les classes CRUD peuvent retourner des résultats surprenants lorsqu'ils sont confrontés à une base de données réelle. Il est préférable de vider ces problèmes dès le début. Souvent les développeurs vont écrire des tests unitaires très minces pour CRUD; tester uniquement les cas les plus bénins. L'utilisation d'une base de données réelle pour vos tests peut tester toutes sortes de cas de coin que vous n'avez peut-être même pas connus.
Cela étant dit, il peut y avoir d'autres problèmes. Après chaque test, vous souhaitez retourner votre base de données à un état connu. C'est mon travail actuel que nous nuke la base de données en exécutant toutes les instructions DROP, puis en recréant complètement toutes les tables à partir de zéro. C'est extrêmement lent sur Oracle, mais peut être très rapide si vous utilisez une mémoire base de données comme HSQLDB. Lorsque nous avons besoin de débusquer les problèmes spécifiques à Oracle, Nous modifions simplement l'URL de la base de données et les propriétés du pilote, puis nous exécutons Oracle. Si vous n'avez pas ce type de portabilité de base de données, Oracle dispose également d'une sorte de fonctionnalité d'instantané de base de données qui peut être utilisée spécifiquement à cette fin; restaurer la base de données entière à un état précédent. Je suis sûr de ce que les autres bases de données ont.
Selon le type de données dans votre base de données, L'API ou la charge approche peut fonctionner mieux ou pire. Lorsque vous avez des données hautement structurées avec de nombreuses relations, les API vous faciliteront la vie en rendant les relations entre vos données explicites. Il vous sera plus difficile de faire une erreur lors de la création de votre ensemble de données de test. Comme mentionné par d'autres affiches outils de refactoring peuvent prendre en charge certains des changements à la structure de vos données automatiquement. Souvent, je trouve utile de penser aux données de test générées par L'API comme composant un scénario; quand un utilisateur / système a fait les étapes X, Y Z et puis les tests iront à partir de là. Ces états peuvent être atteints parce que vous pouvez écrire un programme qui appelle la même API que votre utilisateur utiliserait.
Le chargement des données devient beaucoup plus important lorsque vous avez besoin de gros volumes de données, que vous avez peu de relations entre vos données ou qu'il y a une cohérence dans les données qui ne peuvent pas être exprimées à l'aide D'API ou de mécanismes relationnels standard. Lors d'un travail qui a travaillé à mon équipe a été la rédaction de l'application de rapport pour un grand réseau système d'inspection des paquets. Le volume de données était assez important pour l'époque. Afin de déclencher un sous-ensemble utile de cas de test, nous avons vraiment besoin de données de test générées par les renifleurs. De cette façon, les corrélations entre les informations sur un protocole seraient corrélées avec des informations sur un autre protocole. Il était difficile de capturer cela dans L'API.
La plupart des bases de données ont des outils pour importer et exporter des fichiers texte délimités de tables. Mais souvent, vous ne voulez que des sous-ensembles d'entre eux; faire en utilisant vidages de données plus compliqué. À mon travail actuel, nous devons prendre quelques décharges de données réelles qui sont générées par les programmes Matlab et stockées dans la base de données. Nous avons un outil qui peut vider un sous-ensemble des données de la base de données, puis le comparer avec la "vérité au sol" pour les tests. Il semble que nos outils d'extraction soient constamment modifiés.
Pourquoi ces deux approches définies comme étant exclusivement?
Je ne vois aucun argument viable pour pas utilisant des ensembles de données préexistants, en particulier des données particulières qui ont causé des problèmes dans le passé.
Je ne peux pas voir tout argument viable pour pas étendre par programme ces données avec toutes les conditions possibles vous pouvez imaginer causer des problèmes et même un peu de données aléatoires pour intégration test.
Dans les approches agiles modernes , les tests unitaires sont l'endroit où il importe vraiment que les mêmes tests soient exécutés à chaque fois. En effet, les tests unitaires ne visent pas à trouver des bugs mais à préserver la fonctionnalité de l'application telle qu'elle est développée, permettant au développeur de refactoriser au besoin.
Les tests D'intégration, en revanche, sont conçus pour trouver les bogues auxquels vous ne vous attendiez pas. Exécuter avec Certaines données différentes à chaque fois peut même être bon, à mon avis. Vous devez juste vous assurer que votre test préserve les données défaillantes si vous obtenez un échec. Rappelez-vous, dans les tests d'intégration formelle, l'application elle-même sera gelée sauf pour les corrections de bugs afin que vos tests puissent être modifiés pour tester le nombre maximum possible et les types de bugs. Dans l'intégration, vous pouvez et devez jeter l'évier de cuisine à l'application.
Comme d'autres l'ont noté, bien sûr, tout cela dépend naturellement du type d'application que vous développez et le genre d'organisation dans laquelle vous vous trouvez, etc.
J'utilise généralement des scripts SQL pour remplir les données dans le scénario dont vous discutez.
C'est simple et très facilement reproductible.
Je fais les deux, en fonction de ce que je dois tester:
-
J'importe des données de test statiques à partir de scripts SQL ou de vidages DB. Ces données sont utilisées dans la charge d'objet (désérialisation ou mappage d'objet) et dans les tests de requête SQL (quand je veux savoir si le code retournera le résultat correct).
De plus, j'ai généralement des données backbone (config, valeur pour nommer les tables de recherche, etc.). Ceux-ci sont également chargés dans cette étape. Notez que ce chargement est un test unique (avec la création de la base de données à partir de zéro).
Quand j'ai du code qui modifie la base de données (object - > DB), Je l'exécute généralement contre une base de données vivante (en mémoire ou dans une instance de test quelque part). C'est pour s'assurer que le code fonctionne; pas pour créer une grande quantité de lignes. Après le test, j'annule la transaction (en suivant la règle selon laquelle les tests ne doivent pas modifier l'état global).
Bien sûr, il y a des exceptions à la règle:
- je crée également une grande quantité de lignes dans la performance test.
- Parfois, je dois valider le résultat d'un test unitaire (sinon, le test deviendrait trop gros).
Cela ne répondra probablement pas à toutes vos questions, le cas échéant, mais j'ai pris la décision dans un projet de faire des tests unitaires contre la base de données. J'ai senti dans mon cas que la structure de base de données avait également besoin de tests, c'est-à-dire que ma conception de base de données fournissait ce dont l'application avait besoin. Plus tard dans le projet quand je sentirai que la structure de la base de données est stable, je m'éloignerai probablement de cela.
Pour générer des données, j'ai décidé de créer une application externe qui remplissait la base de données avec des données "aléatoires", j'ai créé un nom de personne et générateurs de nom de société etc.
La raison de faire cela dans un programme externe était: 1. Je pouvais réexécuter les tests sur des données modifiées par test, c'est-à-dire m'assurer que mes tests pouvaient s'exécuter plusieurs fois et que la modification des données effectuée par les tests était valide. 2. Je pourrais si nécessaire, nettoyer la base de données et prendre un nouveau départ.
Je suis d'accord qu'il y a des points d'échec dans cette approche, mais dans mon cas, car par exemple, la génération de personnes faisait partie de la logique métier générant des données pour les tests était en fait tester cette partie aussi.