web-dev-qa-db-fra.com

MVC 3: Comment apprendre à tester avec Nunit, Ninject et MOQ?

Version courte de mes questions:

  1. Quelqu'un peut-il me signaler de bonnes sources détaillées à partir de laquelle je peux apprendre à mettre en œuvre des tests dans mon application MVC 3, à l'aide de Nunit, Ninject 2 et MOQ?
  2. Quelqu'un peut-il ici préciser pour moi comment le découplage du référentiel de contrôleur, la moqueur et l'injection de dépendance travaillent ensemble?

Version plus longue de mes questions:

Ce que j'essaie de faire ...

Je commence actuellement à créer une application MVC 3, qui utilisera l'entité Framework 4, avec une première approche de base de données. Je veux faire ce droit, alors j'essaie de concevoir les classes, les couches, etc., être très testable. Mais, je n'ai que peu d'expérience avec des tests d'unités ou des tests d'intégration, autres qu'une compréhension académique d'eux.

Après beaucoup de recherches, je m'installe d'utiliser

  • Nunit comme mon cadre de test
  • Ninject 2 comme cadre d'injection de dépendance
  • MOQ comme mon cadre moqueur.

Je connais le sujet de quel cadre est le meilleur, etc., pourrait entrer dans cela, mais à ce stade, je ne sais vraiment pas suffisamment à propos de celui-ci pour former une opinion solide. Donc, je viens de décider d'aller avec ces solutions gratuites qui semblent être bien aimées et bien entretenues.

Ce que j'ai appris jusqu'à présent ...

J'ai passé du temps à traverser certaines de ces affaires, à lire des ressources telles que:

À partir de ces ressources, j'ai réussi à faire l'entraînement de la nécessité d'un motif de référentiel, avec des interfaces de référentiel, afin de découpler mes contrôleurs et ma logique d'accès aux données. J'ai déjà écrit une partie de cela dans mon application, mais j'avoue que je ne suis pas clair sur la mécanique de l'ensemble, et si je fais ce découplage à l'appui de l'injection de moqueur ou de dépendance, ou les deux. En tant que tel, je ne voudrais certainement pas avoir entendu parler de vous les gars à ce sujet. Toute clarté que je peux gagner sur ce genre de choses m'aidera à ce stade.

Où les choses ont boueux pour moi ...

Je pensais que je saisis ce genre de choses assez bien jusqu'à ce que je commençais à essayer d'envelopper ma tête autour de Ninject, comme décrit dans Construire des applications testables ASP.NET MVC , cité ci-dessus. Plus précisément, je me suis complètement perdu autour du point dans lequel l'auteur commence à décrire la mise en œuvre d'une couche de service, à la fois dans le document.

Quoi qu'il en soit, je cherche maintenant plus de ressources pour étudier, afin d'essayer d'obtenir diverses perspectives autour de ce truc jusqu'à ce qu'il commence à me donner un sens pour moi.

Résumant tout cela, en faisant bouillir jusqu'à des questions spécifiques, je me demande ce qui suit:

  1. Quelqu'un peut-il me signaler de bonnes sources détaillées à partir de laquelle je peux apprendre à mettre en œuvre des tests dans mon application MVC 3, à l'aide de Nunit, Ninject 2 et MOQ?
  2. Quelqu'un peut-il ici préciser pour moi comment le découplage du référentiel de contrôleur, la moqueur et l'injection de dépendance travaillent ensemble?

Edit:

Je viens de découvrir le wiki officiel ninject sur Github, je vais donc commencer à travailler à travers cela pour voir si cela commence à clarifier des choses pour moi. Mais je suis toujours très intéressé par les pensées de la communauté SO sur tout cela :)

48
campbelt

Si vous utilisez le package NInject.mvc Nuget, une partie de l'article que vous avez associé causait la confusion ne sera pas requis. Ce paquet a tout ce dont vous avez besoin pour commencer à injecter vos contrôleurs, ce qui est probablement le plus gros point de douleur.

Lors de l'installation de ce package, il créera un fichier NInjectMVC3.CS dans le dossier App_Start, à l'intérieur de cette classe est une méthode RegisterServices. C'est là que vous devez créer les liaisons entre vos interfaces et vos implémentations.

private static void RegisterServices(IKernel kernel)  
{  
  kernel.Bind<IRepository>().To<MyRepositoryImpl>();
  kernel.Bind<IWebData>().To<MyWebDAtaImpl>();
}        

Maintenant, dans votre contrôleur, vous pouvez utiliser une injection de constructeur.

public class HomeController : Controller {  
    private readonly IRepository _Repo;
    private readonly IWebData _WebData;

    public HomeController(IRepository repo, IWebData webData) {
      _Repo = repo;
      _WebData = webData;
    }
}

Si vous êtes après une couverture de test très élevée, il faut alors parler à une autre pièce de code logique (Say Controller), vous devez créer une interface et une implémentation), vous devez créer une interface et une implémentation, ajoutez la définition de liaison à regisService et ajoutez un nouvel argument de constructeur. .

Ceci s'applique non seulement au contrôleur, mais à n'importe quelle classe, donc dans l'exemple ci-dessus si votre implémentation de référentiel avait besoin d'une instance de WebData pour quelque chose, vous ajouteriez le champ réadonn et le constructeur à votre mise en œuvre de votre référentiel.

Ensuite, lorsqu'il s'agit de tester, ce que vous voulez faire est de fournir une version moquée de toutes les interfaces requises, de sorte que la seule chose que vous testez est le code de la méthode que vous écrivez le test. Donc, dans mon exemple, disons que l'irepositoire a un

bool TryCreateUser(string username);

Qui est appelé par une méthode de contrôleur

public ActionResult CreateUser(string username) {
    if (_Repo.TryCreateUser(username))
       return RedirectToAction("CreatedUser");
    else
       return RedirectToAction("Error");
}

Ce que vous essayez vraiment de tester ici, c'est que si la déclaration et les types de retour, vous ne souhaitez pas avoir à créer un référentiel réel qui retournera true ou false en fonction de valeurs spéciales que vous le donnez. C'est là que vous voulez vous moquer.

public void TestCreateUserSucceeds() {
    var repo = new Mock<IRepository>();
    repo.Setup(d=> d.TryCreateUser(It.IsAny<string>())).Returns(true);
    var controller = new HomeController(repo);
    var result = controller.CreateUser("test");
    Assert.IsNotNull(result);
    Assert.IsOfType<RedirectToActionResult>(result)
    Assert.AreEqual("CreatedUser", ((RedirectToActionResult)result).RouteData["Action"]);
}

^ Cela ne sera pas compilé pour vous car je connais mieux Xunit et ne vous souvenez pas des noms de propriété sur RedirectToActionResult du sommet de ma tête.

Donc, pour résumer, si vous voulez qu'un seul code de code parle à un autre, tapez une interface entre les deux. Cela vous permet ensuite de se moquer de la deuxième pièce de code de sorte que lorsque vous testez le premier, vous pouvez contrôler la sortie et que vous ne testez que le code en question.
[.____] Je pense que c'était ce point qui a vraiment fait tomber le penny pour moi avec tout cela, vous le faites pas nécessairement parce que le code l'exige, mais parce que le test l'exige.

Un dernier conseil spécifique à MVC, à tout moment, vous devez accéder aux objets Web de base, httpcontext, httpequest, etc., enveloppez toutes ces personnes derrière une interface également (comme les iwebdata dans mon exemple) car pendant que vous pouvez vous moquer de ces utilisateurs. Classes de base, il devient douloureuse très rapidement car ils ont beaucoup de dépendances internes que vous devez également vous moquer.
[.____] aussi avec MOQ, mettez le mockbehaviour à strict lors de la création de simulacres et cela vous dira si quelque chose est appelé que vous n'avez pas fourni de maquette.

60
Chris Sainty
  1. Voici la demande que je crée. Il est open source et disponible sur GitHub et utilise toutes les substances requises - MVC3, Nunit, MOQ, NInject - https://github.com/alexanderbeletsky/trackyt.net/tree/trackyT.net/tree/Master/src Englisons

  2. Le découplage du référentiel de contrôleur est simple. Toutes les opérations de données sont déplacées vers le référentiel. Le référentiel est une implémentation de certains types d'irréprochage. Le contrôleur ne crée jamais de référentiels à l'intérieur de lui-même (avec l'opérateur new) mais les reçoit plutôt par argument ou propriété de constructeur.

.

public class HomeController {
  public HomeController (IUserRepository users) {

  }
}

Cette technique s'appelle "inversion de contrôle". Pour soutenir l'inversion du contrôle, vous devez fournir une "injection de dépendance". Ninject est un bon. Inside Ninject Vous associez une interface particulière avec une classe de mise en œuvre:

Bind<IUserRepository>().To<UserRepository>();

Vous remplacez également l'usine de contrôleur par défaut avec votre personnalisée. À l'intérieur de la personnalisation, vous déléguez l'appel au noyau NINJECT:

public class TrackyControllerFactory : DefaultControllerFactory
{
    private IKernel _kernel = new StandardKernel(new TrackyServices());

    protected override IController GetControllerInstance(
        System.Web.Routing.RequestContext requestContext,
        Type controllerType)
    {
        if (controllerType == null)
        {
            return null;
        }

        return _kernel.Get(controllerType) as IController;
    }
}

Lorsque l'infrastructure MVC est sur le point de créer un nouveau contrôleur, l'appel est délégué à la méthode Custom Controller Factory GetControllerInstance, qui le déléguette à Ninject. Ninject voit que pour créer ce contrôleur, le constructeur a un argument de type IUserRepository. En utilisant la liaison déclarée, elle voit que "j'ai besoin de créer un utilisateur userposity pour satisfaire les besoins de l'iusterexitory." Cela crée l'instance et le transmet au constructeur.

Le constructeur n'est jamais au courant de l'instance exacte qui serait passée à l'intérieur. Tout dépend de la liaison que vous prévoyez cela.

Exemples de code:

9
Alexander Beletsky