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:
Exécutez le code, téléchargez réellement le document, vérifiez auprès de l'API d'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.
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é.
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?
Il y a deux questions que nous devons examiner ici.
Le premier 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, de très rapide tests unitaires à moins rapide tests d'intégration à encore plus lent tests d'acceptation . (Il peut y avoir encore plus de couches éclatées, comme tests fonctionnels .)
La seconde est que vous mélangez les appels au 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 fonctionnent toujours en vert. 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 maquettes 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 en ce qui concerne les simulacres est de pas les types de simulateurs 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 les simuler. 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é). Nous pouvons même le rendre un peu plus abstrait, disons un PersistenceService
plutôt qu'un AmazonS3Bucket
. PersistenceService
serait 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 PersistenceService
autour de l'API S3 (disons un S3PersistenceService
), 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 objet PersistenceService
. Nous utilisons injection de dépendance pour passer notre PersistenceService
dans l'objet. Il est important non pas de 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 maintenant notre PersistenceService
, et notre S3PersistenceService
Fait maintenant tous les appels à l'API S3. Dans nos tests, nous pouvons simuler PersistenceService
, puisque nous le possédons, 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 se 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 du 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 qui lui est 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.
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. Puisqu'il est généralement intelligent de ne pas trop dépendre 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.
Je dirais que cela dépend de la complexité de votre utilisation de l'API.
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.
Vous devez également effectuer des tests supplémentaires qui n'appellent pas réellement l'API, de sorte que vous pouvez 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.
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 fournit des fonctionnalités très similaires à l'API S3 réelle.
Vous pouvez également jeter un œil à LocalStack, un cadre qui combine les outils existants et fournit un environnement de 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.