Les moqueries violent-elles le principe ouvert / fermé?

13

Il y a quelque temps, j'ai lu, sur une réponse Stack Overflow que je ne trouve pas, une phrase qui expliquait que vous devriez tester les API publiques, et l'auteur a dit que vous devriez tester les interfaces. L'auteur a également expliqué que si une implémentation de méthode changeait, vous ne devriez pas avoir besoin de modifier le scénario de test, car cela romprait le contrat garantissant le fonctionnement du système testé. En d'autres termes, un test doit échouer si la méthode ne fonctionne pas, mais pas parce que l'implémentation a changé.

Cela a attiré mon attention lorsque nous parlons de moqueries. Étant donné que le mocking repose fortement sur les appels d'attente du système sous les dépendances du test, les mocks sont étroitement associés à l'implémentation plutôt qu'à l'interface.

Lors de la recherche de simulateurs contre stub , plusieurs articles conviennent que les stubs doivent être utilisés à la place des simulacres, car ils ne reposent pas sur les attentes des dépendances, ce qui signifie que le test n'a pas besoin de connaître le système sous-jacent sous la mise en œuvre du test.

Mes questions seraient:

  1. Les moqueries violent-elles le principe ouvert / fermé?
  2. Y a-t-il quelque chose qui manque dans l'argument en faveur des talons du dernier paragraphe, qui fait que les talons ne sont pas si grands contre les moqueries?
  3. Si oui, quel serait un bon cas d'utilisation pour se moquer et quand serait un bon cas d'utilisation pour utiliser des talons?
Christopher Francisco
la source
8
Since mocking relays heavily on expectation calls from system under test's dependencies...Je pense que c'est là que vous allez de travers. Une maquette est une représentation artificielle d'un système externe. Il ne représente en aucun cas le système externe, sauf dans la mesure où il simule le système externe de manière à permettre l'exécution de tests sur du code ayant des dépendances sur ledit système externe. Vous aurez toujours besoin de tests d'intégration pour prouver que votre code fonctionne avec le vrai système non simulé.
Robert Harvey
8
En d'autres termes, la maquette est une implémentation de remplacement. C'est pourquoi nous avons programmé une interface en premier lieu, afin que nous puissions utiliser des simulations comme stand-in pour la mise en œuvre réelle. En d'autres termes, les simulations sont découplées de la mise en œuvre réelle et non couplées à celle-ci.
Robert Harvey
3
"En d'autres termes, un test doit échouer si la méthode ne fonctionne pas, mais pas parce que l'implémentation a changé", ce n'est pas toujours vrai. Il existe de nombreuses circonstances dans lesquelles vous devez modifier à la fois votre implémentation et vos tests.
whatsisname

Réponses:

4
  1. Je ne vois pas pourquoi les moqueries violeraient le principe ouvert / fermé. Si vous pouviez nous expliquer pourquoi vous pensez qu'ils le pourraient, alors nous pourrons peut-être atténuer vos préoccupations.

  2. Le seul inconvénient des stubs auquel je peux penser est qu'ils nécessitent généralement plus de travail pour écrire que les mocks, car chacun d'eux est en fait une implémentation alternative d'une interface dépendante, il doit donc généralement fournir une version complète (ou convaincante) implémentation de l'interface dépendante. Pour vous donner un exemple extrême, si votre sous-système sous test invoque un SGBDR, alors une maquette du SGBDR répondrait simplement à des requêtes spécifiques connues pour être émises par le sous-système sous test, produisant des ensembles prédéterminés de données de test. D'un autre côté, une implémentation alternative serait un SGBDR complet en mémoire, éventuellement avec le fardeau supplémentaire d'avoir à émuler les caprices du SGBDR client-serveur réel que vous utilisez en production. (Heureusement, nous avons des choses comme HSQLDB, donc nous pouvons en fait faire ça, mais quand même,

  3. Les bons cas d'utilisation pour la simulation sont lorsque l'interface dépendante est trop compliquée pour écrire une implémentation alternative pour elle, ou si vous êtes sûr que vous n'écrirez la simulation qu'une seule fois et ne la toucherez plus jamais. Dans ces cas, allez-y et utilisez une maquette rapide et sale. Par conséquent, les bons cas d'utilisation des stubs (implémentations alternatives) sont à peu près tout le reste. Surtout si vous prévoyez de vous engager dans une relation à long terme avec le sous-système testé, optez définitivement pour une implémentation alternative qui sera agréable et propre, et nécessitera une maintenance uniquement en cas de changement d'interface, au lieu d'exiger une maintenance chaque fois que l'interface changements et chaque fois que la mise en œuvre du sous-système sous test change.

PS La personne à laquelle vous faites référence pourrait être moi, dans l'une de mes autres réponses liées aux tests ici sur programmers.stackexchange.com, par exemple celle-ci .

Mike Nakis
la source
an alternative implementation would be a full-blown in-memory RDBMS- Vous ne devez pas nécessairement aller aussi loin avec un talon.
Robert Harvey
@RobertHarvey bien, avec HSQLDB et H2, il n'est pas si difficile d'aller aussi loin. Il est probablement plus difficile de faire quelque chose à moitié pour ne pas aller aussi loin. Mais si vous décidez de le faire vous-même, vous devrez commencer par écrire un analyseur SQL. Bien sûr, vous pouvez couper certains coins, mais il y a beaucoup de travail . Quoi qu'il en soit, comme je l'ai dit ci-dessus, ce n'est qu'un exemple extrême.
Mike Nakis
9
  1. Le principe Open / Closed consiste principalement à pouvoir changer le comportement d'une classe sans le modifier. Par conséquent, l'injection d'une dépendance de composant simulée dans une classe en cours de test ne la viole pas.

  2. Le problème avec les doubles de test (mock / stub) est que vous faites essentiellement des hypothèses arbitraires concernant la façon dont la classe testée interagit avec son environnement. Si ces attentes sont fausses, eh bien, vous risquez d'avoir des problèmes une fois le code déployé. Si vous pouvez vous le permettre, testez votre code dans les mêmes contraintes que celle qui limite votre environnement de production. Si vous ne le pouvez pas, faites le moins d'hypothèses possibles, et simulez / stubez uniquement les périphériques de votre système (base de données, service d'authentification, client HTTP, etc ...).

La seule raison valable pour laquelle, à mon humble avis, un double doit être utilisé, c'est lorsque vous devez enregistrer ses interactions avec la classe en cours de test, ou lorsque vous devez fournir de fausses données (ce que les deux techniques peuvent faire). Attention cependant, en abuser reflète une mauvaise conception, ou un test qui repose trop sur l'API en cours d'implémentation.

Francis Toth
la source
6

Remarque: je suppose que vous définissez Mock pour signifier "une classe sans implémentation, juste quelque chose que vous pouvez surveiller" et Stub pour être "partiellement mock, aka utilise certains des comportements réels de la classe implémentée", selon cette pile Question de débordement .

Je ne sais pas pourquoi vous pensez que le consensus est d'utiliser des talons, par exemple c'est tout le contraire dans la documentation Mockito

Comme d'habitude, vous allez lire l'avertissement de simulation partielle: la programmation orientée objet est moins gérée par la complexité en divisant la complexité en objets SRPy distincts et spécifiques. Comment la simulation partielle s'intègre-t-elle dans ce paradigme? Eh bien, ce n'est pas le cas ... La simulation partielle signifie généralement que la complexité a été déplacée vers une méthode différente sur le même objet. Dans la plupart des cas, ce n'est pas ainsi que vous souhaitez concevoir votre application.

Cependant, il y a de rares cas où les simulations partielles sont utiles: gérer le code que vous ne pouvez pas changer facilement (interfaces tierces, refactoring provisoire du code hérité, etc.) Cependant, je n'utiliserais pas de simulations partielles pour les nouvelles, pilotées par les tests et bien- code conçu.

Cette documentation le dit mieux que moi. L'utilisation de simulateurs vous permet de simplement tester cette classe particulière, et rien d'autre; si vous avez besoin de moqueries partielles pour obtenir le comportement que vous recherchez, vous avez probablement fait quelque chose de mal, violez SRP, etc., et votre code pourrait tenir lieu de refactor. Les maquettes ne violent pas le principe ouvert-fermé, car elles ne sont utilisées que dans les tests de toute façon, ce ne sont pas de véritables modifications de ce code. Habituellement, ils sont générés à la volée de toute façon par une bibliothèque comme cglib.

durron597
la source
2
À partir de la même question SO fournie (réponse acceptée), voici la définition Mock / Stub à laquelle je me référais également: les objets Mock sont utilisés pour définir les attentes, c'est-à-dire: dans ce scénario, je m'attends à ce que la méthode A () soit appelée avec tel ou tel paramètre. Les simulacres enregistrent et vérifient ces attentes. Les talons, d'autre part, ont un objectif différent: ils n'enregistrent pas ou ne vérifient pas les attentes, mais nous permettent plutôt de «remplacer» le comportement, l'état du «faux» objet afin d'utiliser un scénario de test ...
Christopher Francisco
2

Je pense que le problème peut découler de l'hypothèse que les seuls tests valides sont ceux qui satisfont au test ouvert / fermé.

Il est facile de voir que le seul test qui importe est celui qui teste l'interface. Cependant, en réalité, il est souvent plus efficace de tester cette interface en testant le fonctionnement interne.

Par exemple, il est presque impossible de tester une exigence négative, telle que «la mise en œuvre ne lèvera aucune exception». Considérez une interface de carte implémentée avec une table de hachage. Vous voulez être certain que le hashmap rencontre l'interface de la carte, sans lancer, même quand il doit refaire des choses (ce qui pourrait devenir risqué). Vous pouvez tester chaque combinaison d'entrées pour vous assurer qu'elles répondent aux exigences de l'interface, mais cela peut prendre plus de temps que la mort thermique de l'univers. Au lieu de cela, vous cassez un peu l'encapsulation et développez des simulations qui interagissent plus étroitement, forçant la table de hachage à faire exactement la refonte nécessaire pour garantir que l'algorithme de rehachage ne lance pas.

Tl / Dr: le faire "par le livre" est bien, mais quand le coup de pouce arrive, avoir un produit sur le bureau de votre patron d'ici vendredi est plus utile qu'une suite de tests par le livre qui prend jusqu'à la mort de la chaleur du univers pour confirmer la conformité.

Cort Ammon
la source