2012-02-22 6 views
7

en double possible:
Using IoC for Unit TestingCombinant tests unitaires (se moquant) et un cadre d'injection Dependecy

Je pense que j'ai un problème à comprendre les tests unitaires de manière et/ou injection de dépendance sont travail. J'utilise NUnit et Rhino Mocks pour les tests unitaires et Ninject en tant que Framework d'Incidences de Dépendances. En général, je pense que ces deux-là seraient parfaits - mais d'une certaine manière, il semble que cela devienne de plus en plus compliqué et difficile à comprendre.

(Je vais essayer de donner un bon exemple, pour le garder propre et facile, c'est à propos de moi, faire du vélo).

1.) Sans DI/Tests unitaires:
sans connaître de DI et de tests unitaires, mon code aurait regardé comme ça - et je serais heureux:

public class Person 
{ 
    public void Travel() 
    { 
     Bike bike = new Bike(); 
     bike.Ride(); 
    } 
} 

public class Bike 
{ 
    public void Ride() 
    { 
     Console.WriteLine("Riding a Bike"); 
    } 
} 

monter ma vélo, je voudrais juste besoin: new Person().Travel();

2.) Avec DI:
Je ne veux pas que le couplage serré, donc je besoin d'une interface et un NinjectModule! J'aurais des frais généraux, mais ça ira, tant que le code est facile à lire et à comprendre. Je vais juste passer le code de la classe Person modifiée, la classe de vélo est inchangé:

public class Person 
{ 
    IKernel kernel = new StandardKernel(new TransportationModule()); 
    public void Travel() 
    { 
     ITransportation transportation = kernel.Get<ITransportation>(); 
     transportation.Ride(); 
    } 
} 

je pouvais encore monter mon vélo avec juste: new Person().Travel();

3.) Compte tenu de tests unitaires (sans DI):
Pour être en mesure de vérifier si la méthode Ride est appelée correctement, j'ai besoin d'un Mock. Pour autant que je sache, il existe généralement deux façons d'injecter une interface: Constructor Injection et Setter Injection. Je choisis Injection Constructor pour mon exemple:

public class Person 
{ 
    ITransportation transportation; 

    public person(ITransportation transportation) 
    { 
     this.transportation = transportation; 
    } 

    public void Travel() 
    { 
     transportation.Ride(); 
    } 
} 

Cette fois-ci, je NEET passer le vélo: new Person(new Bike()).Travel();

4.) Avec DI et la préparation des tests unitaires
La classe 3. Considérant Unit-Testing (sans DI) ferait le travail sans modification, mais je devrais appeler new Person(kernel.Get<ITransportation>());. Grâce à cela, j'ai l'impression que je perds le bénéfice de DI - la classe Personne pourrait appeler Voyage sans aucun couplage et aucun besoin de savoir quel genre de classe le transport est. En outre, je pense que cette forme manque beaucoup de la lisibilité de l'exemple 2.

Est-ce ainsi que c'est fait? Ou y a-t-il d'autres façons plus élégantes de réaliser l'injection de dépendances et la possibilité d'effectuer des tests unitaires (et de simuler)?

(En regardant en arrière, il semble que l'exemple est vraiment mauvais - tout le monde devrait savoir quel type d'appareil de transport il monte en ce moment ...

+0

Je pense que vous pourriez être confus au sujet des définitions de l'injection de dépendance (DI) et de l'inversion de contrôle (IoC). Vous êtes le troisième chiffre * met * en œuvre DI (vous avez déplacé la dépendance vers le constructeur), votre deuxième chiffre utilise le conteneur IoC (Ninject) pour résoudre les dépendances. –

+0

zapthedingbat a raison. Vous faites DI dans le troisième exemple, mais n'utilisez pas le conteneur DI, ce qui est bien. Le conteneur DI est facultatif lorsque vous faites DI. – Steven

+1

Exemple "2" n'utilise pas DI, mais un "Service Locator" ... –

Répondre

10

En général, j'essaie d'éviter d'utiliser un conteneur IoC pour mes tests unitaires - il suffit d'utiliser des mocks et des stubs pour transmettre les dépendances.

Votre problème commence dans le scénario 2: Ceci est pas DI - c'est le service locator (anti-)pattern. Pour la vraie dépendance Injection vous devez passer dans vos dépendances, de préférence via l'injection du constructeur.

Scénario 3 a l'air bien, c'est DI et généralement aussi comment vous êtes activé pour tester vos classes isolation - passer dans les dépendances dont vous avez besoin. Je trouve rarement la nécessité d'utiliser un conteneur DI complet pour les tests unitaires, car chaque classe testée aura seulement quelques dépendances, dont chacune peut être stubbed ou mocked pour effectuer le test.

Je voudrais même faire valoir que si vous besoin un conteneur IoC, vos tests ne sont probablement pas assez fins ou vous avez trop de dépendances. Dans le dernier cas, certains refactoring pourraient être dans le but de former des classes agrégées à partir de deux dépendances ou plus que vous utilisez (seulement s'il y a une connexion sémantique bien sûr). Cela finira par laisser tomber le nombre de dépendances à un niveau que vous êtes à l'aise avec. Ce nombre maximum est différent pour chaque personne, je m'efforce personnellement d'en avoir 4 au maximum, au moins je peux les compter d'une part et moquer n'est pas trop un fardeau.

Un dernier argument et crucial contre l'utilisation d'un conteneur IoC dans les tests unitaires est tests comportementaux: Comment pouvez-vous être sûr que la classe en cours de test se comporte de la façon dont vous le souhaitez, si vous n'êtes pas en plein contrôle de vos dépendances? Vous pouvez certainement obtenir cela en écrasant toutes les dépendances avec des types qui définissent des indicateurs pour certaines actions, mais c'est un gros effort. Il est beaucoup, beaucoup plus facile d'utiliser un cadre moqueur comme RhinoMocks ou Moq à vérifier que certaines méthodes ont été appelées avec les arguments que vous spécifiez. Pour cela vous devez mocker les dépendances que vous voulez vérifier les appels, un conteneur IoC ne peut pas vous aider ici.

4

Vous obtenez des choses confuses. La mise en œuvre 3 est meilleure que le numéro 2, car vous n'avez pas besoin de configurer le cadre DI dans vos tests unitaires.

Donc, lors du test de numéro 3 que vous feriez:

ITransportation transportationMock = MockRepository.GenerateStricktMock<ITransportation>(); 

// setup exceptations on your mock 

var person = new Person(transportationMock); 

Le cadre de DI est quelque chose qui est nécessaire uniquement lors de la construction des arbres d'objet dans le code de production. Dans votre code de test, vous avez le contrôle total de ce que vous voulez tester. Lorsque vous testez une classe à l'aide d'une unité, vous vous moquez de toutes les dépendances.

Si vous souhaitez également effectuer des tests d'intégration, vous devez passer un vrai vélo à votre classe de personne et le tester. L'idée de tester des classes dans une isolation totale est que vous pouvez contrôler chaque chemin de code. Vous pouvez faire en sorte que la dépendance renvoie des valeurs correctes ou incorrectes ou vous pouvez même l'envoyer avec une exception. Si tout fonctionne et que vous avez une bonne couverture de code à partir de vos tests unitaires, vous n'aurez besoin que de quelques tests plus importants pour vous assurer que votre DI est correctement câblé.

La clé de l'écriture de code testable consiste à fractionner la création d'objet à partir de la logique métier.

3

Mes 2 cents ...

Bien que le point 2 est un exemple de l'inversion des dépendances (DIP), il utilise le service de localisation modèle, plutôt que l'injection de dépendances.

Votre point 3 illustre l'injection de dépendances, où le conteneur IoC injecte la dépendance (ITransportation) dans le constructeur lors de la construction de Person.

Dans votre application réelle ET le test unitaire, vous souhaitez utiliser le conteneur IoC pour créer une personne (c'est-à-dire ne pas directement une nouvelle personne). Utilisez le modèle de localisateur de service (kernel.Get<Person>();) ou DI (par exemple, Setter) si votre unité de test Unit prend en charge cette fonction.

Ce serait alors Build Up Personne et ses dépendances (à savoir la classe concrète Configuré pour ITransportation) et Injecter qui en personne et (évidemment, dans l'unité de tester votre IoC sera configuré pour la moquée/stub ITransportation)

Enfin, ce sont les dépendances que vous voudriez mocker, c'est-à-dire ITransportation, afin que vous puissiez tester la méthode Transport() de Person.

Puisque Bike n'a pas de dépendances, il peut être testé unitairement directement/indépendamment (vous n'avez pas besoin d'un simulateur pour tester Bike.Ride() sauf si une dépendance est ajoutée à Bike).