Comment tester du code qui dépend d'API complexes (Amazon S3 par exemple)?

13

J'ai du mal à tester une méthode qui télécharge des documents sur Amazon S3, mais je pense que cette question s'applique à toute API non-triviale / dépendance externe. Je n'ai trouvé que trois solutions potentielles mais aucune ne semble satisfaisante:

  1. Exécutez le code, téléchargez réellement le document, vérifiez auprès de l'API AWS qu'il a été téléchargé et supprimez-le à la fin du test. Cela rendra le test très lent, coûtera de l'argent à chaque fois que le test sera exécuté et ne retournera pas toujours le même résultat.

  2. Mock S3. C'est super poilu parce que je n'ai aucune idée des composants internes de cet objet et ça fait mal parce que c'est beaucoup trop compliqué.

  3. Assurez-vous simplement que MyObject.upload () est appelé avec les bons arguments et assurez-vous que j'utilise correctement l'objet S3. Cela me dérange car il n'y a aucun moyen de savoir avec certitude que j'ai utilisé l'API S3 correctement à partir des seuls tests.

J'ai vérifié comment Amazon teste son propre SDK et ils se moquent de tout. Ils ont un assistant de 200 lignes qui se moque. Je ne pense pas que ce soit pratique pour moi de faire de même.

Comment résoudre ce problème?

à ressort
la source
Pas une réponse, mais dans la pratique, nous utilisons les trois approches que vous avez exposées.
jlhonora

Réponses:

28

Il y a deux questions que nous devons examiner ici.

La première est que vous semblez regarder tous vos tests du point de vue des tests unitaires. Les tests unitaires sont extrêmement précieux, mais ne sont pas les seuls types de tests. Les tests peuvent en fait être divisés en plusieurs couches différentes, des tests unitaires très rapides aux tests d' intégration moins rapides aux tests d' acceptation encore plus lents . (Il peut y avoir encore plus de couches éclatées, comme des tests fonctionnels .)

La seconde est que vous mélangez les appels à du code tiers avec votre logique métier, créant des défis de test et éventuellement rendant votre code plus fragile.

Les tests unitaires doivent être rapides et doivent être exécutés souvent. Les dépendances moqueuses permettent de maintenir ces tests en cours d'exécution rapidement, mais peuvent potentiellement introduire des trous dans la couverture si la dépendance change et que la maquette ne change pas. Votre code peut être rompu pendant que vos tests sont toujours verts. Certaines bibliothèques moqueuses vous alerteront si l'interface de la dépendance change, d'autres non.

Les tests d'intégration, d'autre part, sont conçus pour tester les interactions entre les composants, y compris les bibliothèques tierces. Les simulations ne doivent pas être utilisées à ce niveau de test car nous voulons voir comment l'objet réel interagit ensemble. Parce que nous utilisons des objets réels, ces tests seront plus lents et nous ne les exécuterons pas aussi souvent que nos tests unitaires.

Les tests d'acceptation regardent à un niveau encore plus élevé, testant que les exigences pour le logiciel sont remplies. Ces tests s'exécutent sur l'ensemble du système complet qui serait déployé. Encore une fois, aucune moquerie ne doit être utilisée.

Une ligne directrice que les gens ont trouvée utile concernant les simulateurs est de ne pas simuler les types que vous ne possédez pas . Amazon possède l'API de S3 afin qu'ils puissent s'assurer qu'elle ne change pas en dessous d'eux. En revanche, vous n'avez pas ces assurances. Par conséquent, si vous simulez l'API S3 dans vos tests, cela pourrait changer et casser votre code, tandis que tous vos tests sont verts. Alors, comment pouvons-nous tester le code unitaire qui utilise des bibliothèques tierces?

Et bien non. Si nous suivons la directive, nous ne pouvons pas nous moquer des objets que nous ne possédons pas. Mais… si nous possédons nos dépendances directes, nous pouvons nous en moquer. Mais comment? Nous créons notre propre wrapper pour l'API S3. Nous pouvons la faire ressembler beaucoup à l'API S3, ou nous pouvons l'adapter plus étroitement à nos besoins (préféré). On peut même le rendre un peu plus abstrait, disons un PersistenceServiceplutôt qu'un AmazonS3Bucket. PersistenceServiceserait une interface avec des méthodes comme #save(Thing)et #fetch(ThingId), les types de méthodes que nous aimerions voir (ce sont des exemples, vous voudrez peut-être en fait différentes méthodes). Nous pouvons maintenant implémenter un PersistenceServiceautour de l'API S3 (disons a S3PersistenceService), en l'encapsulant loin de notre code appelant.

Passons maintenant au code qui appelle l'API S3. Nous devons remplacer ces appels par des appels à un PersistenceServiceobjet. Nous utilisons l' injection de dépendance pour passer notre PersistenceServicedans l'objet. Il est important de ne pas demander un S3PersistenceService, mais de demander un PersistenceService. Cela nous permet d'échanger l'implémentation lors de nos tests.

Tout le code qui utilisait directement l'API S3 utilise désormais notre PersistenceService, et notre S3PersistenceServiceeffectue désormais tous les appels à l'API S3. Dans nos tests, nous pouvons nous moquer PersistenceService, puisque nous en sommes propriétaires, et utiliser la maquette pour nous assurer que notre code effectue les appels corrects. Mais maintenant, cela laisse comment tester S3PersistenceService. Il a le même problème qu'auparavant: nous ne pouvons pas le tester à l'unité sans appeler le service externe. Donc… nous ne le testons pas à l'unité. Nous pourrions nous moquer des dépendances de l'API S3, mais cela nous donnerait peu ou pas de confiance supplémentaire. Au lieu de cela, nous devons le tester à un niveau supérieur: les tests d'intégration.

Cela peut sembler un peu troublant de dire que nous ne devrions pas tester à l'unité une partie de notre code, mais regardons ce que nous avons accompli. Nous avions un tas de code un peu partout, nous ne pouvions pas faire de tests unitaires qui peuvent maintenant être testés par le biais de PersistenceService. Nous avons notre désordre de bibliothèque tiers limité à une seule classe d'implémentation. Cette classe doit fournir les fonctionnalités nécessaires pour utiliser l'API, mais n'a pas de logique métier externe attachée. Par conséquent, une fois écrit, il devrait être très stable et ne devrait pas changer beaucoup. Nous pouvons compter sur des tests plus lents que nous n'exécutons pas si souvent car le code est stable.

L'étape suivante consiste à écrire les tests d'intégration pour S3PersistenceService. Ceux-ci doivent être séparés par nom ou dossier afin que nous puissions les exécuter séparément de nos tests unitaires rapides. Les tests d'intégration peuvent souvent utiliser les mêmes cadres de test que les tests unitaires si le code est suffisamment informatif, nous n'avons donc pas besoin d'apprendre un nouvel outil. Le code réel du test d'intégration est ce que vous écririez pour votre option 1.

cbojar
la source
la question est de savoir comment exécuter les tests d'intégration ou plutôt e2e pour l'API que vous exposez. Vous ne pouvez pas vous moquer de PersistenceService pour des raisons évidentes. Soit j'ai mal compris quelque chose, soit en ajoutant une autre couche entre l'API d'application et l'API AWS, cela ne vous donne rien de plus que d'avoir plus de facilité à faire des tests unitaires
Yerken
@Yerken En y réfléchissant, je suis presque sûr que je pourrais remplir une autre longue réponse à cette question. Cela pourrait même être utile pour vous, car vous pourriez obtenir plus que ma réponse.
cbojar
4

Vous devez faire les deux.

L'exécution, le téléchargement et la suppression sont un test d'intégration. Il s'interface avec un système externe et peut donc fonctionner lentement. Il ne devrait probablement pas faire partie de chaque build que vous faites localement, mais il devrait faire partie d'une build CI ou d'une build nocturne. Cela compense la lenteur de ces tests et fournit toujours la valeur de le faire tester automatiquement.

Vous avez également besoin de tests non exécutés plus rapides. Comme il est généralement intelligent de ne pas trop dépendre durement d'un système externe (vous pouvez donc échanger des implémentations ou basculer), vous devriez probablement essayer d'écrire une interface simple sur S3 que vous pouvez coder. Modifiez cette interface dans les tests afin que vous puissiez avoir des tests rapides.

Les premiers tests vérifient que votre code fonctionne réellement avec S3, les seconds tests que votre code appelle correctement le code qui parle à S3.

JDT
la source
2

Je dirais que cela dépend de la complexité de votre utilisation de l'API .

  1. Vous devez certainement faire au moins quelques tests qui invoquent réellement l'API S3 et confirment que cela a fonctionné de bout en bout.

  2. Vous devez également certainement effectuer des tests supplémentaires qui n'appellent pas réellement l'API, afin que vous puissiez tester votre propre logiciel de manière adéquate sans invoquer l'API tout le temps.

La question qui reste est: avez-vous besoin de vous moquer de l'API?

Et je pense que cela dépend de ce que vous en faites. Si vous n'effectuez qu'une ou deux actions simples, je ne pense pas que vous ayez besoin de vous donner la peine de créer une maquette. Je serais satisfait de simplement vérifier mon utilisation des fonctions et de faire des tests en direct.

Cependant, si votre utilisation est plus complexe, avec différents scénarios et différentes variables susceptibles d'affecter les résultats, vous devrez probablement le simuler pour effectuer des tests plus approfondis.


la source
1

En plus des réponses précédentes, la question principale est de savoir si (et comment) vous voulez vous moquer de l'API S3 pour vos tests.

Au lieu de se moquer manuellement des réponses S3 individuelles, vous pouvez profiter de certains cadres de simulation existants très sophistiqués. Par exemple, moto offre des fonctionnalités très similaires à l'API S3 actuelle.

Vous pouvez également jeter un œil à LocalStack , un cadre qui combine les outils existants et fournit un environnement cloud local entièrement fonctionnel (y compris S3) qui facilite les tests d'intégration.

Bien que certains de ces outils soient écrits dans d'autres langages (Python), il devrait être facile de faire tourner l'environnement de test dans un processus externe à partir de vos tests en, par exemple, Java / JUnit.

whummer
la source