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?