web-dev-qa-db-fra.com

Comportements de test unitaire sans couplage aux détails de la mise en œuvre

Dans sa conversation TDD, où est-ce que tout s'est trompé , Ian Cooper pousse l'intention originale de Kent Beck de Kent Beck à TDD (à tester les comportements, non spécifiquement) et soutient pour éviter de coupler les tests à la mise en oeuvre.

Dans le cas de comportement comme save X to some data source Dans un système avec un ensemble typique de services et de référentiels, comment peut-on tester l'enregistrement de certaines données au niveau de service, via le référentiel, sans coupler le test aux détails de la mise en œuvre (comme appeler une méthode spécifique)? Évite-t-il ce genre de couplage ne vaut-il pas l'effort/mauvais d'une manière ou d'une autre?

16
Andy Hunt

Votre exemple spécifique est un cas que vous devez habituellement tester en vérifiant si une certaine méthode a été appelée, car saving X to data source signifie communiquer avec une dépendance externe, le comportement que vous devez tester est que la communication se produit comme prév.

Cependant, ce n'est pas une mauvaise chose. Les interfaces frontières entre votre application et ses dépendances externes sont NO sur les détails de la mise en œuvre, en fait, ils sont définis dans l'architecture de votre système; Ce qui signifie qu'une telle limite n'est pas susceptible de changer (ou si elle doit, ce serait le type de changement le moins fréquent). Ainsi, coupler vos tests sur une interface repository ne doit pas vous faire trop de problèmes (si cela le fait, considérez si l'interface ne volait pas de responsabilités de l'application).

Maintenant, considérons uniquement les règles commerciales d'une application, découplée à partir de l'interface utilisateur, des bases de données et d'autres services externes. Ceci est que vous devez être libre de modifier à la fois la structure et le comportement du code. C'est ici que les tests de couplage et les détails de la mise en œuvre vous obligeront à modifier davantage de code de test que le code de production, même lorsqu'il n'y a pas de changement dans le comportement général de l'application. C'est là que le test State au lieu de Interaction vous aide à aller plus vite.

PS: Ce n'est pas mon intention de dire si le test de l'état ou des interactions est le seul moyen véritable de TDD - je pense que c'est une question d'utiliser le bon outil pour le bon travail.

8
MichelHenrich

Mon interprétation de cette conversation est:

  • composants de test, non classes.
  • tester des composants via leurs ports d'interface.

Ce n'est pas indiqué dans la conversation, mais je pense que le contexte supposé pour le conseil est quelque chose comme:

  • vous développez un système pour les utilisateurs, non, par exemple, une bibliothèque de services publics ou un cadre.
  • l'objectif du test est de avec succès livrer autant que possible dans un budget concurrentiel.
  • les composants sont écrits en une seule, mature, probablement typée de manière statique, comme C #/Java.
  • un composant est de l'ordre des lignes 10000-50000; un projet maven ou vs, plugin OSGI, etc.
  • les composants sont écrits par un seul développeur ou une équipe étroitement intégrée.
  • vous suivez la terminologie et l'approche de quelque chose comme la architecture hexagonale
  • un port de composant est l'endroit où vous quittez la langue locale et son système de type, derrière, basculant vers http/sql/xml/octets/...
  • emballage Chaque port est des interfaces dactylographiées, dans le sens Java/C #, qui peut avoir des implémentations de mise sous tension pour changer de technologies.

Ainsi, le test d'un composant est la plus grande portée possible dans laquelle quelque chose peut encore être raisonnablement appelé test unitaire. Ceci est plutôt différent de la façon dont certaines personnes, en particulier des universitaires, utilisent le terme. Ce n'est rien comme les exemples du tutoriel d'outil de test d'unité typique. Cependant, il correspond à son origine dans les tests matériels; Les panneaux et les modules sont testés une unité, non des fils et des vis. Ou au moins vous ne construisez pas une simulacre boeing pour tester une vis ...

Extrapoler de cela et jetant dans certaines de mes propres pensées,

  • Chaque interface va être une entrée, une sortie ou un collaborateur (comme une base de données).
  • vous test les interfaces d'entrée; Appelez les méthodes, affirmez les valeurs de retour.
  • vous mock les interfaces de sortie; Vérifiez que les méthodes attendues sont appelées à un cas de test donné.
  • vous faux les collaborateurs; fournir une implémentation simple mais opérationnelle

Si vous faites cela correctement et proprement, vous avez à peine besoin d'un outil moqueur; Il est utilisé seulement quelques fois par système.

Une base de données est généralement un collaborateur, il est donc falsifié plutôt que de se moquer. Ce serait douloureux de mettre en œuvre à la main; Heureusement, de telles choses exister déjà .

Le modèle de test de base est une séquence d'opérations (par exemple, économiser et recharger un document); confirmer ça fonctionne. C'est la même chose que pour tout autre scénario de test; Aucune modification de mise en œuvre (travail) est susceptible de causer un tel test échouer.

L'exception est la suivante: les enregistrements de base de données sont écrits mais ne sont jamais lus par le système sous test; par exemple. journaux d'audit ou similaires. Ce sont des sorties, et cela devrait donc être moqué. Le modèle de test est une séquence d'opérations; Confirmez que l'interface d'audit a été appelée avec des méthodes et des arguments comme spécifié.

Notez que même ici, vous fournissez un outil de moqueur de type-coffre-fort comme Makito , renommer une méthode d'interface ne peut pas provoquer une défaillance de test. Si vous utilisez un IDE avec les tests chargés, il sera refactored avec la méthode Renommer. Si vous ne le faites pas, le test ne compilera pas.

7
soru

Ma suggestion est d'utiliser une approche de test basée sur l'État:

DONNÉ Nous avons le test DB dans un état connu

LORSQUE Le service est appelé avec des arguments x

PUIS Affirmation que la DB a changé de son état d'origine à l'état attendu en appelant des méthodes de référentiel en lecture seule et en vérifiant leurs valeurs retournées.

Faire de cela, vous ne vous appuyez pas sur un algorithme interne du service et vous êtes libre de refacturer sa mise en œuvre sans avoir à modifier les tests.

Le seul couplage ici concerne l'appel de la méthode de service et les appels de référentiel nécessaires pour lire les données de la DB, qui va bien.

0
Elifarley