J'ai tourné en rond en essayant de trouver la meilleure façon de tester à l'unité une bibliothèque cliente API que je développe. La bibliothèque a une Client
classe qui a essentiellement un mappage 1: 1 avec l'API, et une Wrapper
classe supplémentaire qui fournit une interface plus conviviale par-dessus le Client
.
Wrapper --> Client --> External API
J'ai d'abord écrit un tas de tests contre les deux Client
et Wrapper
, en fait, je teste simplement qu'ils transmettent aux fonctions appropriées de tout ce qui fonctionne ( Wrapper
fonctionne Client
, etClient
fonctionne sur une connexion HTTP). J'ai commencé à me sentir mal à l'aise, cependant, car j'ai l'impression de tester l'implémentation de ces classes plutôt que l'interface. En théorie, je pourrais changer les classes pour avoir une autre implémentation parfaitement valide, mais mes tests échoueraient car les fonctions que je m'attendais à appeler ne sont pas appelées. Cela ressemble à des tests fragiles pour moi.
Après cela, j'ai pensé à l'interface des classes. Les tests devraient vérifier que les classes font réellement le travail qu'elles sont censées faire, plutôt que la façon dont elles le font. Alors, comment puis-je faire cela? La première chose qui me vient à l'esprit est de bloquer les demandes d'API externes. Cependant, je suis nerveux à l'idée de simplifier à l'excès le service externe. De nombreux exemples d'API tronqués que j'ai vus donnent des réponses prédéfinies, ce qui semble être un moyen très simple de tester uniquement que votre code s'exécute correctement contre votre fausse API. L'alternative est de se moquer du service, ce qui est tout simplement irréalisable, et devrait être tenu à jour chaque fois que le service réel change - cela ressemble à une surpuissance et à une perte de temps.
Enfin, je lis ceci d' une autre réponse sur les programmeurs SE :
Le travail d'un client API distant consiste à émettre certains appels - ni plus, ni moins. Par conséquent, son test doit vérifier qu'il émet ces appels - ni plus, ni moins.
Et maintenant, je suis plus ou moins convaincu - lors des tests Client
, tout ce que j'ai besoin de tester est qu'il fait les bonnes requêtes à l'API (Bien sûr, il y a toujours la possibilité que l'API change mais mes tests continuent de passer - mais c'est où les tests d'intégration seraient utiles). Comme il ne Client
s'agit que d'un mappage 1: 1 avec l'API, ma préoccupation avant de passer d'une implémentation valide à une autre ne s'applique pas vraiment - il n'y a qu'une seule implémentation valide pour chaque méthode de Client
.
Cependant, je suis toujours coincé avec la Wrapper
classe. Je vois les options suivantes:
Je stoppe la
Client
classe et teste simplement que les méthodes appropriées sont appelées. De cette façon, je fais la même chose que ci-dessus, mais en traitant leClient
comme un remplaçant pour l'API. Cela me ramène là où j'ai commencé. Encore une fois, cela me donne la sensation inconfortable de tester la mise en œuvre, pas l'interface. LeWrapper
pourrait très bien être implémenté en utilisant un client complètement différent.Je crée une maquette
Client
. Maintenant, je dois décider jusqu'où aller avec la moquerie - créer une maquette complète du service demanderait beaucoup d'efforts (plus de travail que ce qui est allé dans la bibliothèque elle-même). L'API elle-même est simple, mais le service est assez complexe (c'est essentiellement une banque de données avec des opérations sur ces données). Et encore une fois, je devrai garder ma maquette en phase avec le réelClient
.Je teste simplement que les requêtes HTTP appropriées sont en cours. Cela signifie que
Wrapper
sera appelé via unClient
objet réel pour effectuer ces requêtes HTTP, donc je ne le teste pas réellement de manière isolée. Cela en fait un peu un test unitaire terrible.
Je ne suis donc pas particulièrement satisfait de ces solutions. Qu'est-ce que tu ferais? Y a-t-il une bonne façon de procéder?
Réponses:
TLDR : Malgré la difficulté, vous devez bloquer le service et l'utiliser pour les tests d'unité client.
Je ne suis pas certain que "le travail d'un client d'API distant consiste à émettre certains appels, ni plus, ni moins ...", à moins que l'API ne soit constituée que de points de terminaison qui renvoient toujours un statut fixe et ne consomment ni ne produisent toutes les données. Ce ne serait pas l'API la plus utile ...
Vous souhaitez également vérifier que le client envoie non seulement les demandes correctes, mais qu'il gère correctement le contenu des réponses, les erreurs, les redirections, etc. Et testez tous ces cas.
Comme vous le constatez, vous devriez avoir des tests d'intégration qui couvrent la pile complète de wrapper -> client -> service -> DB et au-delà, mais pour répondre à votre question principale, sauf si vous avez un environnement où les tests d'intégration peuvent être exécutés dans le cadre de chaque CI construit sans beaucoup de maux de tête (bases de données de test partagées, etc.), vous devriez investir du temps dans la création d'un stub de l'API.
Le stub vous permettra de créer une implémentation fonctionnelle du service, mais sans avoir à implémenter de couche en dessous du service lui-même.
Vous pouvez envisager d'utiliser une solution basée sur DI pour y parvenir, avec une implémentation du modèle de référentiel sous les ressources REST:
Quoi qu'il en soit, à moins que le stubbing et / ou la refactorisation du service ne soit hors de question, politiquement ou pratiquement, j'entreprendrais probablement quelque chose de similaire à ce qui précède. Si ce n'est pas possible, je m'assurerais d'avoir une très bonne couverture d'intégration et de les exécuter aussi souvent que possible compte tenu de votre configuration de test disponible.
HTH
la source