2008-11-24 7 views
10

Je comprends la nécessité de tester une classe qui a de la logique (par exemple, celle qui peut calculer des remises), où vous pouvez tester la réelle classe.Pourquoi est-ce que j'écrirais une fausse classe et que l'unité la testerait?

Mais je viens de commencer à écrire des tests unitaires pour un projet qui servira de référentiel (obtenir des objets à partir d'une base de données). Je me retrouve à écrire un 'faux' référentiel qui implémente une interface ISomethingRepository. Il utilise un Dictionary<Guid, Something> pour le stockage interne. Il implémente les méthodes Add(Something) et GetById(Guid) de l'interface.

Pourquoi est-ce que j'écris ceci? Rien de ce que j'écris ne sera réellement utilisé par le logiciel quand il sera déployé, n'est-ce pas? Je ne vois pas vraiment la valeur de cet exercice.

J'ai également reçu le conseil d'utiliser un objet fantaisie que je peux configurer à l'avance pour répondre à certaines attentes. Cela me semble encore plus inutile: bien sûr, le test va réussir, je me suis moqué/truqué pour réussir! Et je suis toujours pas sûr que le logiciel lui-même fonctionnera comme il se doit lors de la connexion à la base de données ...

confus ...

me point que quelqu'un peut dans la bonne direction pour me aider à comprendre ce ?

Merci!

+0

Je suis upvoting ceci, parce que c'est quelque chose qui me irrite souvent. Les défenseurs les plus rapides de TDD ont tendance à tomber dans ce accident, et finissent par tester des scénarios/codes qui ne se produisent pas dans la réalité. – FlySwat

Répondre

13

Vous ne testez pas votre objet fantôme mais une autre classe qui interagit avec lui. Ainsi, vous pouvez par exemple tester qu'un contrôleur transfère un appel de méthode d'enregistrement vers votre faux référentiel. Il y a quelque chose qui ne va pas si vous "testez vos faux objets"

+1

Oui, c'est ce que je pensais. Alors j'ai une mauvaise idée de tester mon code d'accès aux données. Par exemple: http://tinyurl.com/6qojvh –

+0

Désolé, est-ce une question? – mmiika

+0

Eh bien, je me demandais en quelque sorte quelle était votre opinion sur cet article dans le contexte de notre discussion. –

0

Vous écrivez une classe "fausse" appelée objet Stub ou Mock parce que vous voulez tester une implémentation de manière simple sans tester la vraie classe concrète. Le but est de simplifier le test en testant uniquement l'interface (ou la classe abstraite).

Dans votre exemple, vous testez quelque chose qui a un dictionnaire. Il pourrait être rempli en réel par la base de données ou avoir beaucoup de logique derrière. Dans votre "faux" objet, vous pouvez tout simplifier en ayant toutes les données constantes. De cette façon, vous ne testez que le comportement de l'interface et non pas comment l'objet concret est construit.

+0

Je comprends, mais en quoi cela a-t-il de la valeur? J'ai écrit le * faux * et l'ai testé. Il n'est pas utilisé dans le code de production. Alors pourquoi l'écrire et le tester? –

+1

Pour tester votre implémentation sans avoir dans votre chemin d'élément concret. Vous pouvez sortir beaucoup de code et vous concentrer sur le but de vos tests. Mon exemple dans mon post est bon ... la base de données un ... vous n'avez pas besoin d'avoir une "vraie base de données" et vous pouvez le truquer ... –

2

Le but de l'objet simulé/stub est de ne pas tester au lieu de l'unité que vous essayez de tester, il est de vous permettre de tester cette unité sans avoir besoin d'autres classes. En gros, vous pouvez tester les classes une à la fois sans avoir à tester toutes les classes dont elles dépendent aussi.

+0

Oui, c'est ce que je pensais. Donc, dans une couche «supérieure» qui utilise normalement le code du référentiel, je pourrais injecter un faux pour les tests afin qu'il ne se connecte pas réellement à la base de données, correct? –

+2

Juste sur celui-là –

1

Qui regarde les observateurs?

Il est intéressant, par exemple, que l'implémentation fictive lance des exceptions spécifiques pour les cas d'angle, ainsi vous savez que les classes qui utilisent ou dépendent de IRepositorySomething peuvent gérer les exceptions qui sont lancées dans la vie réelle. Certaines de ces exceptions ne peuvent pas être générées facilement avec une base de données de test.

Vous ne testez pas l'objet Mock avec un test unitaire, mais vous l'utilisez pour tester les classes qui en dépendent.

+0

J'ai trouvé créer des circonstances exceptionnelles pour être l'une des choses les plus utiles que vous pouvez faire avec un simulacre. +1 –

3

Ne pas tester la classe de simulation. Testez la classe de production à l'aide de la classe Faux.

Le point entier de la classe de support de test est d'avoir quelque chose que vous pouvez prédire son comportement. Si vous devez tester la classe de support de test afin de prédire son comportement, il y a un problème.

Dans l'article de base de données factice que vous avez lié dans un commentaire, l'auteur doit tester sa fausse base de données parce que c'est son produit (au moins dans le contexte de l'article).

Modifier: les termes mis à jour sont plus cohérents.

  • Mock - créé en se moquant cadre
  • Faux - créé manuellement, pourrait effectivement fonctionner certains.
  • Support de test - Mocks, Fakes, Stubs, et tout le reste. Pas de production.
+0

"Tester la classe de production en utilisant la classe mock" Ok, donc si ma classe d'accès aux données de production utilise NHibernate, je devrais mocker NHibernate, puis tester la classe de production? Donc, la partie NHibernate de la classe de production devrait être injectable. Et pas besoin d'écrire une fausse classe d'accès aux données? –

+1

Droite. vous devriez vous moquer de NHib, et n'avez pas besoin de fournir un faux. À moins que le contrat public de NHib soit trop large ... alors vous devriez envelopper NHib et se moquer de l'emballage. –

+0

Mais puisque votre dataacess enveloppe déjà NHib du reste de l'application ... moquez votre dataacess pour tester le reste de l'application (plus de valeur). –

1

Vous ne devriez pas tester la classe Faux. Ce que vous faites normalement est: vous créez des classes fictives pour toutes les classes avec lesquelles la classe que vous testez interagissent.

Supposons que vous testiez une classe appelée Bicycle qui prend en compte les objets constructeurs des classes Wheel, Saddle, HandleBar, etc.

Et puis dans la classe Bike vous voulez tester tester sa méthode GetWeight qui itére probablement à travers chaque partie et appelle la propriété/méthode Poids d'entre eux, puis renvoie le total.

Ce que vous faites:

  • vous écrire une classe fantaisie pour chaque partie (roue, selle, etc.) qui simplement implémente le poids bit
  • alors vous passer ces classes simulacres à la bicyclette
  • test de la méthode GetWeight sur la classe de vélos

ce que la façon dont vous pouvez f ocus sur le test du GetWeight sur la classe de vélos, d'une manière indépendante sur d'autres classes (disent qu'ils ne sont pas encore mis en œuvre, non etc. déterministe)

1

Au lieu d'écrire une fausse classe par vous-même, vous pouvez utiliser un outil (comme Rhino ou Typemock) pour le simuler. C'est beaucoup plus facile que d'écrire tous les mocks soi-même. Et comme d'autres l'ont dit, il n'est pas nécessaire de tester un faux code, ce qui n'est pas un code si vous utilisez l'outil.

1

J'ai trouvé deux utilisations pour les classes fictives que nous utilisons dans les tests d'implémentation de référentiel. Le premier consiste à tester les services qui utilisent une implémentation de l'équivalent "ISomethingRepository" que vous avez mentionné. Cependant, nos implémentations de référentiel sont créées par une usine. Cela signifie que nous écrivons des tests sur le "ISomethingRepository", mais pas directement sur le "MockSomethingRepository". En testant l'interface, on peut facilement affirmer que la couverture de code pour nos tests couvre 100% de l'interface.Les révisions de code permettent de vérifier simplement que les nouveaux membres de l'interface sont testés. Même si les développeurs s'exécutent sur le simulacre retourné par l'usine, le serveur de génération a une configuration différente qui teste l'implémentation concrète que l'usine retourne dans les versions nocturnes. Il offre le meilleur des deux mondes, en termes de couverture de test et de performance locale.

La deuxième utilisation est celle que je suis surpris que personne d'autre n'a mentionné. Mon équipe est responsable du niveau intermédiaire. Nos développeurs Web sont responsables de la partie frontale des produits Web. En élaborant des mises en œuvre de référentiels fictifs, il n'y a pas d'obstacle artificiel à attendre que la base de données soit modélisée et mise en œuvre avant le début du travail d'avant-projet. Il est possible d'écrire des vues qui seront construites à partir de la maquette afin de fournir une quantité minimale de «vraies» données pour répondre aux attentes des développeurs Web. Par exemple, des données peuvent contenir des données de chaîne de longueur minimale et maximale pour vérifier que ni leur implémentation, ni leur implémentation, etc.

Puisque les usines que nous utilisons sont connectées à quel "ISomethingRepository" retourner, nous avons des configurations de test locales , nous construisons des configurations de test, des configurations de production, etc. Nous essayons délibérément de nous assurer qu'aucune équipe sur le projet n'a des temps d'attente déraisonnables en raison du temps de mise en œuvre d'une autre équipe. La plus grande partie du temps d'attente est toujours fournie par l'équipe de développement, mais nous sommes en mesure de lancer nos objets de domaine, nos référentiels et nos services à un rythme plus rapide que le développement frontal.

Bien sûr, YMMV. ;-)

0

Jetez un oeil à l'article suivant pour une bonne explication de ceci:

http://msdn.microsoft.com/en-us/magazine/cc163358.aspx

Fondamentalement, si vous écrivez un objet faux et il se révèle être assez complexe, il est parfois Cela vaut la peine de tester le faux unitaire pour s'assurer qu'il fonctionne comme prévu.

Étant donné qu'un référentiel peut être complexe, il est souvent judicieux d'écrire des tests unitaires.

0

Il n'est généralement pas nécessaire d'exécuter des tests unitaires classiques dans la couche d'accès aux données. Vous pouvez peut-être écrire un test d'unité de style intégrateur pour vos classes Data Acess, c'est-à-dire Integration Test (= intégration de votre code de couche d'accès aux données avec la base de données) en utilisant les fonctionnalités Unit Testing Frameworks. Par exemple, dans un projet Spring, vous pouvez utiliser Spring Testcontext pour démarrer votre contexte Spring dans un test unitaire, puis vous connecter à une base de données réelle et tester que les requêtes renvoient des résultats corrects. Vous avez probablement besoin d'une propre base de données pour les tests unitaires, ou peut-être vous pouvez les connecter avec un développeur DB.

Questions connexes