8
votes

Unité testant une usine abstraite qui prend des paramètres

Étant donné une implémentation d'usine abstraite:

public class FooFactory : IFooFactory {
   public IFoo Create(object param1, object param2) {
      return new Foo(param1, param2);
   }
}


6 commentaires

Les tests d'unités doivent dépendre de l'exigence fonctionnelle et des attentes sur le composant testé. Je ne vois aucune valeur de l'unité testant cette classe sans le reste du contexte.


@IVOWIBLO Je pense que vous confondez des tests d'unité avec des tests de style BDD. Si vous n'écrivez pas un test pour cet appareil, comment savez-vous si cela fonctionne?


faire le test de l'unité, mais que allez-vous tester? Quelle est l'attente de cette unité? J'ai dit que je ne vois pas la valeur de faire un test d'unité de cette classe sans rien savoir d'autre. Faire des tests unitaires juste en raison de tests d'unité n'a pas de sens du tout. Des tests (unités, comportements, fonctionnalités complètes, tout ce que vous voulez) devraient toujours être motivés par des attentes et des exigences. Sinon, c'est juste snobisme (si ce mot existe).


@IVOWIBLO Le test garantit que les paramètres corrects sont transmis à la création de FOO.


Et quelle est la valeur de cela, en plus d'améliorer la couverture?


Je trouve la valeur dans l'amélioration de la couverture: en le gardant haut, il est plus facile de localiser les zones qui ont besoin de test.


5 Réponses :


0
votes

Vous pouvez rendre FOOFACTORY une classe générique qui s'attend à FOO en tant que paramètre et spécifiez-le pour être une simule. Calling Factory.create (...) crée ensuite une instance de la mock, et vous pourriez alors confirmer que le motif a reçu les arguments que vous attendez.


2 commentaires

Cher Gödel Non. Cela teste un détail de mise en œuvre.


@Jason: pire ... Cela soulève la question de faire une usine pour cette usine pour simplifier la génération de l'objet, mais si JONTYMC demande ...



0
votes

Cela dépend de ce que FOO fait avec les 2 objets d'entrée. Vérifiez que quelque chose qui foo fait avec eux qui peut facilement être interrogé. Par exemple, si les méthodes (y compris les getters ou les setters) sont appelées sur les objets, fournissent des simulacres ou des talons que vous pouvez vérifier si les choses attendues ont été appelées. S'il y a quelque chose sur l'IFOO qui peut être testé, vous pouvez transmettre des valeurs connues dans vos objets simulés pour tester après la création. Les objets eux-mêmes ne doivent pas nécessairement être accessibles au public pour vérifier que vous les avez réussi.

Je pourrais également vérifier que le type attendu (FOO) est renvoyé, en fonction de si les autres comportements testés dépendent de la différence entre les implémentations IFOO.

S'il y a des conditions d'erreur pouvant en résulter, j'essaierais de forcer les personnes aussi, que se passe-t-il si vous passez à NULL pour des objets ou des objets, etc. FOO serait testé séparément, bien sûr, afin que vous puissiez Supposons lors d'un test FOOFACTORY que FOO fait ce qu'il devrait.


13 commentaires

Vous testeriez le type de retour? Qu'est-ce que sur Terre est-ce qui vous achète? Vous avez mis en place toutes ces abstractions et vous allez maintenant écrire un test qui se casse dès que vous modifiez un détail de mise en œuvre. Yikes.


@Jason Si je vais écrire un test pour plusieurs usines qui créent une implémentation IFOO, je vérifierais que chacun retourne le type de béton que j'attends. Je questionne la nécessité de tester une usine qui fait si peu d'appel à appeler nouveau, mais dans le domaine de ce qui a été demandé ...


Non. Ce n'est pas que les consommateurs de comportement de l'usine ne peuvent dépendre de (le type de retour est si ) afin de ne pas être testé.


@Jason Les "vrais" consommateurs ne doivent absolument pas dépendre du type de retour, vous avez raison. Mais, les tests peuvent en fonction de ce que vous devez faire pour vérifier le comportement correct. Je vais modifier pour dire "pourrait", car il est vrai que ce n'est pas toujours quelque chose à rechercher.


Les tests ne devraient pas en dépendre non plus! Ils sont aussi réels que tout autre consommateur.


@Thatatchkguy "Dans le domaine de ce qui a été posé ..." J'ai offert la possibilité de ne pas les tester unitaires et laissez-la aux tests d'intégration. Je soupçonne que c'est ce que la plupart des gens font, mais je me demandais si j'avais manqué quelque chose.


Non, ceci est définitivement pour les tests d'unités. Différentes implémentations de l'interface d'usine peuvent avoir un comportement attendu différent. Ceci est clairement pour le domaine des tests unitaires.


@JONTYMC J'ai répondu à votre question en supposant que c'était un exemple hypothétique. Dans ce cas, j'ai essayé de répondre aussi largement que possible pour donner quelques idées pour votre (ou quiconque lisant cela plus tard) situation exacte. Si toute votre usine est vraiment appelée nouvelle, alors non, je ne voudrais pas tester cela. Si c'est plus impliqué, cela peut justifier des tests d'unités.


@Jason pour une usine, une partie de leur comportement attendu renvoie le type que vous attendez. Si une usine peut renvoyer une variété d'implémentations IFOO en fonction des entrées, etc., cela fait partie du comportement et je voudrais tester qu'il renvoie le "correct".


De nouveau. Le type de retour est ifoo , donc personne ne peut dépendre de l'un type de béton étant renvoyé. Pour ce faire, il est de s'appuyer sur un détail de mise en œuvre qui peut avoir une chance sans ce qui serait considéré comme des changements de rupture (ceux qui ne dépendent pas des détails de la mise en œuvre). Heck, l'usine pourrait renvoyer une implémentation de ifoo sur un appel et une autre implémentation sur le suivant. Si vous avez besoin du type de béton, appelez simplement nouveau foo ; Pourquoi déranger avec l'usine? Ou, au moins, ne tapez pas le type de retour comme ifoo , tapez-le comme foo .


@Jason, plus je pense à cela, dans la plupart des situations, je conviens que même les tests ne devraient pas s'en soucier. Mais il peut y avoir des moments où il s'agit d'un comportement testable, même si les vrais consommateurs ne s'en soucient jamais. Considérons une usine censée créer une iconnection (ou une iconnecturefactory), étant donné une chaîne de configuration. Je voudrais m'assurer que cela me donnait le bon type de connexion. Le passage d'une connexion de pipe init sur un objet HTTPS ne va pas faire beaucoup de bien. Si l'usine vous donne le mauvais type, tous les tests passeraient, mais vous auriez toujours un problème.


Eh bien, maintenant tu m'as fait repenser ma position légèrement. Si la spécification ne dit pas de donner une chaîne de connexion à une base de données SQL Server renvoie une instance de SQLConnection , vous ne pouvez pas dépendre de ce comportement et ne le testez pas pour le retourner. iconnection qui vous permet de vous connecter à la base de données spécifiée dans la chaîne de connexion). Toutefois, si la spécification indique que vous récupérez un sqlconnection , cela peut être dépendant et doit être testé pour. De même, si la spécification indique FOOFACTORY.CREATE renvoie un FOO , testez-le. Je pense que nous nous sommes rencontrés au milieu. :-)


@Jason La chose amusante à ce sujet est qu'en raison de refactoring, je viens de supprimer ce type de test de 1 des très rares endroits où j'ai eu la nécessité de le faire.



0
votes

Les usines ne sont généralement pas testées par unité, au moins en fonction de mon expérience - il n'y a pas beaucoup de valeur dans l'unité les tester . Parce que c'est la mise en œuvre de cette cartographie de mise en œuvre de l'interface, il fait référence à la mise en œuvre concrète. En tant que tel, la moqueur FOO n'est pas possible de vérifier si elle reçoit ces paramètres passés.

D'autre part, des conteneurs généralement diaboliques fonctionnent comme des usines, donc si vous utilisez un, vous n'auriez pas besoin d'une usine.


2 commentaires

Des usines abstraites sont nécessaires lorsqu'une dépendance repose sur des paramètres seulement connus au moment de l'exécution ou à la durée de vie est plus courte que celle du consommateur: Stackoverflow.com/Questtions/1993397/...


La plupart des cadres de DI feraient le passage des paramètres au moment de l'exécution. Ils peuvent également faire la gestion de la vie.



2
votes

Eh bien, probablement ces paramètres rendent le renvoi ifoo avoir quelque chose de vrai à ce sujet. Testez pour cela être vrai sur l'instance renvoyée.


3 commentaires

Cela ne signifie-t-il pas que vous auriez besoin de savoir sur les internes de l'IFOO en question?


Non, vous passez dans ces paramètres pour une raison pour obtenir un comportement attendu. Quel est ce comportement? Test pour cela. La spécification pour FOOFACTORY.CREATE devrait dire quelque chose comme "crée un objet qui implémente ifoo tel que p (param1, param2) P est un prédicat dépendant de param1 et param2 ". Testez que p (param1, param2) est vrai pour l'objet retourné!


Donc ... supposant que FOO est non trivial et déjà couvert par N tests, vous devez essentiellement écrire n nouveaux tests presque identiques pour tester que le comportement de l'instance FOO retourné est cohérente avec l'entrée à la méthode de création. . Cela ressemble à ce que vous seriez indirectement couplant les tests. Les problèmes de maintenabilité suivront.



11
votes

Voici comment j'écrirais l'un des couple de tests unitaires pour une telle usine (avec xunit.net ): Xxx

fait-il enfreindre l'encapsulation? Oui et non ... un peu. L'encapsulation est non seulement sur la cache de données - plus important encore, il s'agit de Protéger les invariants d'objets . < / p>

Supposons que FOO expose cette API publique: xxx

tandis que l'objet tandis1 violez légèrement le Droit de Demeter , il ne plaisante pas avec les invariants de la classe car il est en lecture seule.

En outre, le < Code> Object1 Une propriété fait partie de la classe FOO concret - non pas l'interface IFOO: xxx

une fois que vous avez réalisé cela dans une API couplée lâche, Les membresConnete sont des détails de la mise en œuvre , de telles propriétés en lecture seule concrètes ont un impact très faible sur l'encapsulation. Pensez-y de cette façon:

Le Constructeur public public est une partie de l'API de la classe de béton FOO, donc simplement en inspectant l'API publique, nous apprenons que param1 et param2 font partie de la classe. Dans un sens, cela "casse d'encapsulation déjà", ce qui rend chaque paramètre disponible en tant que propriétés en lecture seule sur la classe de béton ne change pas beaucoup.

Ces propriétés fournissent l'avantage que nous pouvons maintenant tester le Forme structurelle de la classe FOO renvoyée par l'usine.

C'est beaucoup plus facile que de répéter un ensemble de tests d'unités comportementales qui, nous devons supposer, couvrent déjà la classe FOO concrète. C'est presque comme une preuve logique:

  1. Des tests de la classe FOO concret Nous savons qu'il utilise / interagit correctement avec ses paramètres de constructeur.
  2. à partir de ces tests, nous savons également que le paramètre Constructor est exposé comme une propriété en lecture seule.
  3. à partir d'un test de FOOFACTORY, nous savons qu'il renvoie une instance de la classe FOO concrète.
  4. En outre, nous savons des tests de la méthode de création que ses paramètres sont correctement transmis au FOO Constructor.
  5. q.e.d.

1 commentaires

Oui, a du sens. J'aime l'idée de différencier l'API publique de l'interface et de la classe de béton.