Je travaille avec beaucoup d'applications Web qui sont pilotées par des bases de données de complexité variable sur le backend. En règle générale, il y a un couche ORM distincte de la logique métier et de la présentation. Cela rend les tests unitaires de la logique métier assez simples; les choses peuvent être implémentées dans des modules discrets et toutes les données nécessaires au test peuvent être truquées par le biais de la simulation d'objets.
Mais tester l'ORM et la base de données elle-même a toujours été semé d'embûches et de compromis.
Au fil des ans, j'ai essayé quelques stratégies, dont aucune ne m'a complètement satisfait.
Chargez une base de données de test avec des données connues. Exécutez des tests par rapport à l'ORM et confirmez que les bonnes données reviennent. L'inconvénient ici est que votre base de données de test doit suivre les modifications de schéma dans la base de données d'application et peut se désynchroniser. Il s'appuie également sur des données artificielles et ne peut pas exposer les bogues qui se produisent en raison d'une entrée utilisateur stupide. Enfin, si la base de données de test est petite, elle ne révélera pas des inefficacités comme un index manquant. (OK, ce dernier n'est pas vraiment pour quoi les tests unitaires devraient être utilisés, mais cela ne fait pas de mal.)
Chargez une copie de la base de données de production et testez-la. Le problème ici est que vous n'avez peut-être aucune idée de ce qui se trouve dans la base de données de production à un moment donné; vos tests devront peut-être être réécrits si les données changent au fil du temps.
Certaines personnes ont souligné que ces deux stratégies reposent sur des données spécifiques et qu'un test unitaire ne devrait tester que la fonctionnalité. À cette fin, j'ai vu suggéré:
- Utilisez un serveur de base de données factice et vérifiez uniquement que l'ORM envoie les requêtes correctes en réponse à un appel de méthode donné.
Quelles stratégies avez-vous utilisées pour tester les applications basées sur une base de données, le cas échéant? Qu'est-ce qui vous a le mieux fonctionné?
la source
Réponses:
J'ai en fait utilisé votre première approche avec un certain succès, mais de manière légèrement différente, je pense que cela résoudrait certains de vos problèmes:
Conservez l'intégralité du schéma et des scripts de création dans le contrôle de code source afin que tout le monde puisse créer le schéma de base de données actuel après une extraction. En outre, conservez des exemples de données dans des fichiers de données qui sont chargés par une partie du processus de génération. Lorsque vous découvrez des données qui provoquent des erreurs, ajoutez-les à vos exemples de données pour vérifier que les erreurs ne réapparaissent pas.
Utilisez un serveur d'intégration continue pour créer le schéma de base de données, charger les exemples de données et exécuter des tests. C'est ainsi que nous gardons notre base de données de test synchronisée (en la reconstruisant à chaque test). Bien que cela nécessite que le serveur CI ait accès et possède sa propre instance de base de données dédiée, je dis que la création de notre schéma db 3 fois par jour a considérablement aidé à trouver des erreurs qui n'auraient probablement pas été trouvées avant la livraison (sinon plus tard) ). Je ne peux pas dire que je reconstruis le schéma avant chaque commit. Est-ce que quelqu'un? Avec cette approche, vous n'aurez pas à le faire (enfin peut-être que nous devrions, mais ce n'est pas un gros problème si quelqu'un oublie).
Pour mon groupe, la saisie utilisateur se fait au niveau de l'application (et non db), ce qui est testé via des tests unitaires standard.
Chargement de la copie de la base de données de production:
c'était l'approche utilisée lors de mon dernier emploi. Ce fut une énorme cause de douleur pour quelques problèmes:
Serveur de base de données moqueur:
Nous faisons également cela dans mon travail actuel. Après chaque validation, nous exécutons des tests unitaires par rapport au code d'application qui ont injecté des accesseurs mock db. Ensuite, trois fois par jour, nous exécutons la version complète de la base de données décrite ci-dessus. Je recommande définitivement les deux approches.
la source
J'exécute toujours des tests sur une base de données en mémoire (HSQLDB ou Derby) pour ces raisons:
La base de données en mémoire est chargée de nouvelles données une fois les tests démarrés et après la plupart des tests, j'appelle ROLLBACK pour la maintenir stable. TOUJOURS garder les données dans la base de données de test stable! Si les données changent tout le temps, vous ne pouvez pas tester.
Les données sont chargées à partir de SQL, d'une base de données de modèle ou d'une sauvegarde / sauvegarde. Je préfère les vidages s'ils sont dans un format lisible car je peux les mettre dans VCS. Si cela ne fonctionne pas, j'utilise un fichier CSV ou XML. Si je dois charger d'énormes quantités de données ... je ne le fais pas. Vous n'avez jamais à charger d'énormes quantités de données :) Pas pour les tests unitaires. Les tests de performances sont un autre problème et des règles différentes s'appliquent.
la source
Je pose cette question depuis longtemps, mais je pense qu'il n'y a pas de solution miracle pour cela.
Ce que je fais actuellement, c'est se moquer des objets DAO et garder en mémoire une bonne collection d'objets qui représentent des cas intéressants de données qui pourraient vivre sur la base de données.
Le principal problème que je vois avec cette approche est que vous ne couvrez que le code qui interagit avec votre couche DAO, mais que vous ne testez jamais le DAO lui-même, et d'après mon expérience, je constate que de nombreuses erreurs se produisent également sur cette couche. Je garde également quelques tests unitaires qui s'exécutent sur la base de données (dans le but d'utiliser TDD ou des tests rapides localement), mais ces tests ne sont jamais exécutés sur mon serveur d'intégration continue, car nous ne conservons pas de base de données à cet effet et je pense que les tests qui s'exécutent sur le serveur CI doivent être autonomes.
Une autre approche que je trouve très intéressante, mais qui ne vaut pas toujours car elle prend un peu de temps, consiste à créer le même schéma que vous utilisez pour la production sur une base de données intégrée qui s'exécute uniquement dans le cadre des tests unitaires.
Même s'il ne fait aucun doute que cette approche améliore votre couverture, il existe quelques inconvénients, car vous devez être aussi proche que possible de ANSI SQL pour le faire fonctionner à la fois avec votre SGBD actuel et le remplacement intégré.
Peu importe ce que vous pensez être plus pertinent pour votre code, il existe quelques projets qui peuvent le rendre plus facile, comme DbUnit .
la source
Même s'il existe des outils qui vous permettent de se moquer de votre base de données d'une manière ou d'une autre (par exemple jOOQ d »
MockConnection
, qui peut être vu dans cette réponse - disclaimer, je travaille pour le fournisseur de jOOQ), je vous conseille pas se moquer de grandes bases de données avec complexes requêtes.Même si vous voulez simplement tester l'intégration de votre ORM, sachez qu'un ORM émet une série très complexe de requêtes vers votre base de données, qui peuvent varier en
Se moquer de tout cela pour produire des données factices sensibles est assez difficile, à moins que vous ne construisiez en fait une petite base de données à l'intérieur de votre maquette, qui interprète les instructions SQL transmises. Cela dit, utilisez une base de données de tests d'intégration bien connue que vous pouvez facilement réinitialiser avec des données bien connues, sur lesquelles vous pouvez exécuter vos tests d'intégration.
la source
J'utilise le premier (exécution du code sur une base de données de test). Le seul problème de fond que je vous vois soulever avec cette approche est la possibilité que les schémas se désynchronisent, que je traite en conservant un numéro de version dans ma base de données et en apportant toutes les modifications de schéma via un script qui applique les modifications pour chaque incrément de version.
J'apporte également toutes les modifications (y compris au schéma de la base de données) à mon environnement de test, donc cela finit par être l'inverse: une fois tous les tests réussis, appliquez les mises à jour du schéma à l'hôte de production. Je garde également une paire distincte de bases de données de test et d'application sur mon système de développement afin de pouvoir vérifier que la mise à niveau de la base de données fonctionne correctement avant de toucher la ou les boîtes de production réelles.
la source
J'utilise la première approche mais un peu différente qui permet de résoudre les problèmes que vous avez mentionnés.
Tout ce qui est nécessaire pour exécuter des tests pour les DAO se trouve dans le contrôle de code source. Il comprend un schéma et des scripts pour créer la base de données (le docker est très bon pour cela). Si la base de données intégrée peut être utilisée - je l'utilise pour la vitesse.
La différence importante avec les autres approches décrites est que les données requises pour le test ne sont pas chargées à partir de scripts SQL ou de fichiers XML. Tout (sauf certaines données de dictionnaire qui sont effectivement constantes) est créé par l'application à l'aide de fonctions / classes utilitaires.
Le but principal est de rendre les données utilisées par le test
Cela signifie essentiellement que ces utilitaires permettent de spécifier de manière déclarative uniquement les choses essentielles pour le test dans le test lui-même et d'omettre les choses non pertinentes.
Pour donner une idée de ce que cela signifie dans la pratique, considérez le test de certains DAO qui fonctionnent avec les
Comment
s àPost
s écrits parAuthors
. Afin de tester les opérations CRUD pour un tel DAO, certaines données doivent être créées dans la base de données. Le test ressemblerait à:Cela présente plusieurs avantages par rapport aux scripts SQL ou aux fichiers XML avec des données de test:
Rollback vs Commit
Je trouve plus pratique que les tests soient validés lorsqu'ils sont exécutés. Premièrement, certains effets (par exemple
DEFERRED CONSTRAINTS
) ne peuvent pas être vérifiés si la validation ne se produit jamais. Deuxièmement, lorsqu'un test échoue, les données peuvent être examinées dans la base de données car elles ne sont pas annulées par la restauration.Cela a pour inconvénient que le test peut produire des données cassées et cela entraînera des échecs dans d'autres tests. Pour y faire face, j'essaie d'isoler les tests. Dans l'exemple ci-dessus, chaque test peut en créer de nouvelles
Author
et toutes les autres entités sont créées en relation avec celui-ci, les collisions sont donc rares. Pour gérer les invariants restants qui peuvent être potentiellement cassés mais ne peuvent pas être exprimés en tant que contrainte de niveau DB, j'utilise des vérifications programmatiques pour les conditions erronées qui peuvent être exécutées après chaque test (et elles sont exécutées en CI mais généralement désactivées localement pour des performances) les raisons).la source
PostBuilder.post()
. Il génère des valeurs pour tous les attributs obligatoires de la publication. Ce n'est pas nécessaire dans le code de production.Pour un projet basé sur JDBC (directement ou indirectement, par exemple JPA, EJB, ...), vous pouvez créer une maquette non pas la base de données entière (dans ce cas, il serait préférable d'utiliser une base de données de test sur un vrai SGBDR), mais uniquement une maquette au niveau JDBC .
L'avantage est l'abstraction qui vient de cette façon, car les données JDBC (jeu de résultats, nombre de mises à jour, avertissement, ...) sont les mêmes quel que soit le backend: votre base de données de prod, une base de données de test ou juste quelques données de maquette fournies pour chaque test Cas.
Avec la connexion JDBC simulée pour chaque cas, il n'est pas nécessaire de gérer la base de données de test (nettoyage, un seul test à la fois, recharger les appareils, ...). Chaque connexion maquette est isolée et il n'est pas nécessaire de nettoyer. Seuls les équipements requis minimaux sont fournis dans chaque scénario de test pour simuler l'échange JDBC, ce qui permet d'éviter la complexité de la gestion d'une base de données de test entière.
Acolyte est mon framework qui comprend un pilote JDBC et un utilitaire pour ce type de maquette: http://acolyte.eu.org .
la source