Je cherche des conseils sur l'essai des stratégies pour le service à la communication de service.
J'ai un seul service (service A) qui effectue un appel à un autre service (B) - qui est une API de repos. Les deux services sont la propriété de moi.
J'ai quelques tests unitaires autour des appels de service et je me moque simplement la bibliothèque HTTP donc pas les demandes sont effectivement envoyées au service. Cela fonctionne bien pour les tests unitaires, mais je me demandais si cela vaut la peine d'ajouter des tests d'intégration que test réel les appels de service et les réponses.
Le problème que je vois est le service B met à jour une base de données pour les tests d'intégration en service A devront réinitialiser les changements qu'ils font en appelant le DB directement. Pour moi, cela ne semble pas idéal car maintenant le service A sait plus sur la mise en œuvre du service B que prévu.
Ces tests sont précieux? Quand je l'ai vu ce genre de tests avant sont souvent fragiles et reposent sur des environnements de développement étant en bon état. Si cela était une API tiers par exemple, je n'aurais pas des tests qui appellent directement.
Je peux penser à deux options:
Écrivez les tests d'intégration dans le service A et ont ces tests font appel la base de données de services B pour réinitialiser/données incrustées au besoin.
Bâton avec simulacres et ne pas ajouter des tests d'intégration au service A. ajouter au lieu des tests fonctionnels au service B qui testent les différents points d'extrémité de repos.
Des conseils ou pensées?
À mon expérience, nos tests ne doit pas être lié par des dépendances Quelle exécution est hors de notre contrôle.
Tout d'abord, réduisons la portée des tests. Comme indiqué dans la question, le service sous test est [~ # ~] A [~ # ~ ~], concentrons donc sur les tests [~ # ~ # ~ # ~] quelle que soit la propriété de [~ # ~] b [~ # ~] et son état (travaillant, courir, etc.).
Une chose importante à réaliser avec des tests est le déterminisme . La seule façon de garantir le comportement correct de [~ # ~ # ~] a [~ # ~] (selon les locaux des (non) exigences fonctionnelles) implémente des tests qui permettent nous reproduire les cas pour satisfaire. Encore et encore, peu importe le moment ou l'environnement.
Un moyen de réaliser le déterminisme est d'implémenter test double. Mocks, stubs, mannequins, espions ... afin que nous puissions reproduire (par programmation ou configuration) les scénarios que nous devons couvrir.
Certains pourraient affirmer que ce n'est pas un test d'intégration. Pour moi, les intégrations pourraient impliquer plusieurs composants travaillant ensemble de toutes sortes de natures. Certains sont sous notre contrôle, d'autres ne le sont pas. Est bon d'être capable d'isoler toutes les intégrations afin que nous puissions vérifier le comportement de notre composant sous plusieurs conditions d'intégration différentes. C'est particulièrement bon lorsque nous pouvons isoler des dépendances qui ralentissent nos tests, car il ralentit également notre temps de marché.
Le problème que je vois est le service B met à jour une base de données de sorte que tout test d'intégration de service A devra réinitialiser les modifications qu'ils apportent en appelant directement la DB directement.
[~ # ~] B [~ # ~ ~] écrire dans dB est presque anecdotique. Le problème est en train de tester un real Service B parce que nous sommes-nous-mêmes - test-test [~ # ~] B [~ # ~] code et environnement où == [~ # ~] B [~ # ~] est en cours d'exécution!
Le vrai danger est sur les conditions inconnues sous lesquelles [~ # ~] B [~ # ~] Vivre au moment des tests. Ces conditions, dans le pire des cas, pourraient faire passer notre test de ne pas passer. S'ils échouent, ils le font en raison de problèmes non liés au code. Celles-ci échouent ne nous donnent pas de commentaires significatifs sur l'état du code testé.
Comme commenté, l'écriture à DB est anecdotique, il y a beaucoup plus de choses qui pourraient aller mal.
Vous devriez être de me demander Pourquoi les tests non déterministes sont dangereux ? Je suggère de lire le blog de Fowler sur erronant non-déterminisme dans les tests et consultez les questions suivantes Question aussi. La réponse de DOC résume très bien le sujet.
Des exemples de tests non déterministes sont Tests flaky. Les tests flaky sont des tests qui échouent en raison de circonstances indéterminées. Ces tests échouent de temps en temps et nous ne savons pas pourquoi. Nous ne pouvons pas reproduire le problème.
Une suite de tests avec des tests flagiques peut devenir une victime de ce que Diana Vaughan appelle normalisation de la déviance - L'idée qu'avec le temps, nous pouvons devenir si habitués aux choses qui ont tort que nous commençons à les accepter comme étant normal et pas un problème.
Construire des microservices - par Sam Newman
Le normalisation de la déviance est la graine du mal.
Des conseils ou des pensées?
Lors du test d'intégration, ni les données ni le comportement du service externe ne devraient vous inquiéter. Au moins pas encore. 1
Ce qui devrait vous inquiéter est de tester la consommation correcte d'interface (API) et de la bonne gestion des commentaires (manutention d'erreur, désérialisation, mappages, etc.). En d'autres termes, le contrat .
Dernièrement, j'ai commencé à travailler avec le concept de Test double et tests de contrats basés sur le consommateur avec des résultats très positifs.
Il est vrai qu'ils nécessitent des efforts supplémentaires abordés pour construire et maintenir ces tests. C'est notre cas. Cependant, nous avons réduit le bâtiment, les tests et le temps de déploiement de manière significative et nous obtenons des commentaires plus rapides et plus significatifs de CI.
Conformément à la réponse de l'écriture ci-dessus et de la réponse de @ Justin, vous pourriez être intéressé par des outils tels que Montebank .
1: Il existe une place pour les tests adressés à valider le comportement réel des services externes. Ils peuvent être placés hors du pipeline de construction. Ils pourraient ou non être essentiels pour un déploiement vert. Cela dépend de savoir si vous pouvez ou non contourner les problèmes soulevés par le service. C'est presque une question politique plutôt que technique.
Vous avez raison, un test qui accède au calque de base de données directement sera plus fragile, mais en fonction de la valeur que le test offre peut encore en valoir la peine. par exemple. Si vous testez une fonctionnalité importante et fragile dans une application héritée, la valeur fournie par ce test peut bien valoir le coût de la maintenance de ce test fragile.
Cela dit, il y a quelques approches alternatives que vous pourriez trouver utiles.
Configurez vos services/tests de sorte que les modifications puissent être réinitialisées via l'API, afin que les modifications ne nécessitent pas de réinitialisation. Par exemple, si votre test crée un utilisateur, exposez un point final qui vous permet de supprimer ou de désactiver les utilisateurs ou de créer des utilisateurs de test avec un préfixe unique pour chaque exécution de test et d'ignorer ceux avec un préfixe différent. N'oubliez pas que la testabilité est une caractéristique (très précieuse) de logiciels - vous ne devriez pas sentir qu'il est d'une manière d'une manière ou d'une autre manière introduite des caractéristiques exclusivement pour améliorer la testabilité de votre logiciel.
Testez contre un faux serveur HTTP, par exemple. Une facturation simulée qui enregistre les demandes reçues et envoie des réponses appropriées en fonction du test d'essai. Cela présente l'inconvénient de ne pas tester l'interaction avec le service "réel", cependant fournit une couverture que les tests d'unités ne peuvent pas. En fait, ce type de test peut fournir une couverture de scénarios pouvant être plus difficile contre le service "réel", par exemple. tester les réponses d'erreur ou une latence élevée.
Vos tests d'intégration ne doivent pas impliquer directement la DB. Eh bien, la question est de savoir quelle interaction vous souhaitez tester:
+-----+ +-----+ +------+
| A |<---->| B |<---->| DB |
+-----+ +-----+ +------+
Essayez-vous de tester l'interaction A-B ou l'interaction A-B-DB? Si vous souhaitez uniquement tester le sous-système A-B et que le service B est une sorte d'abstraction sur la base de données où aucun autre service n'est supposé écrire sur la base de données, vous ne devez pas accéder à la DB même pour vos tests.
Le problème le plus important est que B n'est plus libre de modifier la base de données sans également mettre à jour vos tests d'intégration A-B: Renommer des tableaux, ajout de colonnes, modifier la technologie DB, etc.
Le moyen le plus simple de tester A-B de manière isolée consiste à lancer une instance A et B séparée uniquement pour le test. La DB est effectivement une partie de B, de sorte que vous souhaitez également commencer par une nouvelle base de données. Il est parfois possible de créer une nouvelle base de données par test qui serait idéal. Par exemple. Créer une nouvelle base de données SQLite est super simple. Si la configuration d'un DB est plus impliquée, vous pouvez conserver une DB autour de tests d'intégration qui seront réinitialisés avant chaque test.
Les tests d'intégration sont différents des tests unitaires. Dans les tests unitaires, vous souhaitez effectuer tous les tests isolés. Cela n'est pas réalisable pour les tests d'intégration car l'initialisation de l'environnement est souvent très impliquée et prend beaucoup de temps. Il est souvent préférable d'organiser vos étuis de test individuels dans des suites de tests, où chaque cas de test dépend des résultats du test précédent. L'environnement n'est initialisé que au début de chaque suite de tests. En échange de tests plus rapides, vous payez avec des résultats de test moins utiles: Si un cas de test anticipée dans une suite de tests échoue, les tests restants ne peuvent pas être exécutés.
Un moyen simple d'ajouter des tests d'intégration à un système existant consiste à enregistrer des interactions réelles, puis de les rejouer pour le test. Par exemple. J'ai écrit une fois un outil qui pourrait analyser la sortie du journal d'un système: Pour créer une nouvelle TESTCASE, je copierais l'entrée et la sortie du fichier journal, anonymiseriez les données et enregistrez-la aux fichiers testname.in.txt
et testname.out.txt
. Le coureur de test passera ensuite à travers un répertoire rempli de ces fichiers, rejouerait l'entrée et diffère le résultat avec la sortie attendue. Cependant, vous devez prendre soin de sélectionner des cas de test représentatifs. Répéter des tests similaires est une perte de temps.