web-dev-qa-db-fra.com

Est-il recommandé de se moquer de la classe de béton?

La plupart des exemples donnés dans le site Web du cadre moqueur servent à se moquer d'Interface. Supposons que je remplace actuellement par NSubstitute, tous leurs exemples moqueurs consistent à se moquer de l'interface.

Mais en réalité, j’ai vu à la place un développeur simuler une classe de béton. Est-il recommandé de se moquer de la classe de béton?

34
DonDon

En théorie, il n’ya absolument aucun problème à se moquer d’une classe concrète; nous testons par rapport à une interface logique (plutôt qu’à un mot clé interface) et il importe peu que cette interface logique soit fournie par un class ou un interface

En pratique .NET/C # rend cela un peu problématique. Comme vous avez mentionné un framework .NET moqueur, je vais supposer que vous êtes limité à cela. 

Dans .NET/C #, les membres n'étant pas virtuels par défaut, aucune méthode basée sur un proxy de comportement de moquage (c'est-à-dire dérivant de la classe et écrasant tous les membres pour effectuer des tâches spécifiques au test) ne fonctionnera pas à moins de marquer explicitement les membres. comme virtual. Cela pose un problème: vous utilisez une instance d’une classe fictive censée être totalement sûre dans votre test unitaire (c’est-à-dire que vous n’exécuterez pas de code réel), mais si vous vous êtes assuré que tout est bien virtual, vous risquez de vous retrouver avec un mélange de code réel et de code simulé en cours d'exécution (cela peut être particulièrement problématique s'il existe une logique de constructeur, qui s'exécute toujours et est aggravée s'il existe d'autres dépendances concrètes à créer).

Il y a plusieurs façons de contourner ce problème. 

  • Utilisez interfaces. Ceci fonctionne et c'est ce que nous conseillons dans la documentation NSubstitute , mais a l'inconvénient de gonfler potentiellement votre base de code avec des interfaces qui peuvent ne pas être réellement nécessaires. On peut dire que si nous trouvons de bonnes abstractions dans notre code, nous aurons naturellement des interfaces propres et réutilisables que nous pourrons tester. Je ne l'ai pas encore vu se dérouler comme ça, mais en YMMV. :)
  • Faites le tour avec diligence en rendant tout virtuel. Un inconvénient discutable à cela est que nous suggérons que tous ces membres sont destinés à être des points d'extension dans notre conception, alors que nous voulons simplement changer le comportement de la classe entière pour les tests. Cela n'empêche pas non plus la logique du constructeur de s'exécuter ni d'aider si la classe concrète nécessite d'autres dépendances.
  • Utilisez la réécriture d'assembly via quelque chose comme le complément Virtuosity pour Fody , que vous pouvez utiliser pour modifier la virtualisation de tous les membres de la classe dans votre Assembly.
  • Utilisez une bibliothèque mocking non basée sur un proxy, telle que TypeMock (payé) , JustMock (payé) , Microsoft Fakes (requiert VS Ultimate/Enterprise, bien que son prédécesseur, Microsoft Moles , soit gratuit) ou Prig (libre + open source) . Je crois qu'ils sont capables de se moquer de tous les aspects des classes, ainsi que des membres statiques.

Une plainte commune contre la dernière idée est que vous testez via une "fausse" couture; nous sortons des mécanismes normalement utilisés pour étendre le code afin de changer le comportement de notre code. Le besoin de sortir de ces mécanismes pourrait indiquer une rigidité dans notre conception. Je comprends cet argument, mais j’ai vu des cas où le bruit de la création d’une autre interface l'emportait sur les avantages. Je suppose que c'est une question de conscience du problème de conception potentiel; Si vous n'avez pas besoin des résultats des tests pour mettre en évidence la rigidité de la conception, ce sont d'excellentes solutions.

Une dernière idée que je vais suggérer est de jouer avec la modification de la taille des unités dans nos tests. En règle générale, nous avons une seule classe en tant qu'unité. Si nous avons un certain nombre de classes cohésives comme unité et que les interfaces agissent comme une frontière bien définie autour de ce composant, nous pouvons éviter de simuler autant de classes que de se contenter d'une frontière plus stable. Cela peut rendre nos tests plus compliqués, avec l'avantage que nous testons une unité cohérente de fonctionnalités et que nous sommes encouragés à développer des interfaces solides autour de cette unité.

J'espère que cela t'aides.

67
David Tchepak

Mise à jour

3 ans plus tard, je veux admettre que j'ai changé d'avis. 

Même en théorie, je n'aime toujours pas créer d'interfaces pour faciliter la création d'objets fictifs. En pratique (j'utilise NSubstitute), il est beaucoup plus facile d'utiliser Substitute.For<MyInterface>() plutôt que de simuler une classe réelle avec plusieurs paramètres, par exemple. Substitute.For<MyCLass>(mockedParam1, mockedParam2, mockedParam3), où chaque paramètre doit être simulé séparément. Autres problèmes potentiels décrits dans Documentation NSubstitute

Dans notre entreprise, la pratique recommandée consiste maintenant à utiliser des interfaces.

Réponse originale

Si vous n'êtes pas obligé de créer plusieurs implémentations de la même abstraction, ne créez pas d'interface. Comme signalé par David Tchepak , vous ne voulez pas gonfler votre base de code avec des interfaces qui peuvent ne pas être réellement nécessaires.

De http://blog.ploeh.dk/2010/12/02/InterfacesAreNotAbstractions.aspx

Extrayez-vous les interfaces de vos classes pour permettre un couplage lâche ? Si tel est le cas, vous avez probablement une { relation 1: 1 entre vos interfaces Et les classes concrètes } qui les implémente. C'est probablement pas un bon signe et enfreint le principe utilisé (RAP) .

Avoir une seule implémentation d'une interface donnée est une odeur de code.

Si votre cible est la testabilité, je préfère la deuxième option de réponse de David Tchepak ci-dessus }.

Cependant, je ne suis pas convaincu qu'il faille tout rendre virtuel. Il suffit de ne rendre virtuelles que les méthodes que vous allez substituer. J'ajouterai également un commentaire à côté de la déclaration de la méthode. Cette méthode est virtuelle uniquement pour la rendre substituable à la simulation de test d'unité.

Notez cependant que la substitution de classes concrètes à la place d'interfaces présente certaines limites. E.g. pour NSubstitute

Remarque: les substituts récursifs ne seront pas créés pour les classes, car la création et l'utilisation de classes Peuvent avoir des effets secondaires potentiellement indésirables.

.

10

La question est plutôt: pourquoi pas?

Je peux penser à quelques scénarios où cela est utile, comme:

L'implémentation d'un cours concret n'est pas encore terminée, ou le type qui l'a fait n'est pas fiable. Je me moque donc de la classe telle qu'elle est spécifiée et teste mon code par rapport à elle.

Il peut également être utile de simuler des classes faisant des choses comme l’accès à une base de données. Si vous ne disposez pas d'une base de données de tests, vous voudrez peut-être renvoyer pour vos tests des valeurs toujours constantes (ce qui est facile en se moquant de la classe).

1
konqi

Ce n'est pas que cela soit recommandé, c'est que vous pouvez le faire si vous n'avez pas d'autre choix. 

En règle générale, les projets bien conçus reposent sur la définition d'interfaces pour vos composants distincts afin de pouvoir tester chacun d'eux séparément et en se moquant des autres. Mais si vous travaillez avec un code existant/un code que vous n'êtes pas autorisé à modifier et que vous voulez toujours tester vos classes, vous n’avez pas le choix et vous ne pouvez pas être critiqué pour cela (en supposant que vous ayez fait l’essai ces composants aux interfaces et se sont vu refuser le droit de).

0
jolivier