48 votes

MVC 3 : Comment apprendre à tester avec NUnit, Ninject et Moq ?

Version courte de mes questions :

  1. Quelqu'un peut-il m'indiquer de bonnes sources détaillées à partir desquelles je pourrais comment mettre en place des tests dans mon application MVC 3, en utilisant NUnit, Ninject 2 et Moq ?
  2. Est-ce que quelqu'un ici peut m'aider à clarifier comment Controller-Repository le découplage, le mocking, et l'injection de dépendances fonctionnent ensemble ?

Version plus longue de mes questions :

Ce que j'essaie de faire ...

Je commence actuellement à créer une application MVC 3, qui utilisera Entity Framework 4, avec une approche basée sur la base de données. Je veux faire les choses correctement, et j'essaie donc de concevoir les classes, les couches, etc. pour qu'elles soient hautement testables. Mais je n'ai que peu ou pas d'expérience en matière de tests unitaires ou de tests d'intégration, si ce n'est une compréhension académique de ces derniers.

Après de nombreuses recherches, j'ai opté pour l'utilisation de

  • NUnit comme cadre de test
  • Ninject 2 comme cadre d'injection de dépendances
  • Moq comme cadre de simulation.

Je sais que la question de savoir quel est le meilleur cadre, etc., pourrait entrer en ligne de compte, mais à ce stade, je n'en sais vraiment pas assez pour me forger une opinion solide. J'ai donc décidé d'opter pour ces solutions gratuites qui semblent bien appréciées et bien entretenues.

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

J'ai passé un certain temps à travailler sur ces questions, en lisant des ouvrages tels que

A partir de ces ressources, j'ai réussi à travailler sur la nécessité d'un modèle 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'admets que je ne suis pas clair quant à la mécanique de l'ensemble, et si je fais ce découplage en supportant le mocking, ou l'injection de dépendance, ou les deux. En tant que tel, je ne serais certainement pas contre l'idée d'entendre ce que vous avez à dire à ce sujet. Toute clarté que je peux obtenir sur ce sujet m'aidera à ce stade.

Là où les choses se gâtent pour moi ...

Je pensais que je comprenais assez bien ces choses jusqu'à ce que j'essaie de me familiariser avec Ninject, comme décrit dans Construire des applications ASP.NET MVC testables cité plus haut. Plus précisément, je me suis complètement perdu au moment où l'auteur commence à décrire la mise en œuvre d'une couche de service, à peu près à la moitié du document.

Quoi qu'il en soit, je cherche maintenant d'autres ressources à étudier, afin d'essayer d'obtenir différentes perspectives sur ce sujet jusqu'à ce qu'il commence à avoir un sens pour moi.

En résumant tout cela, en le ramenant à des questions spécifiques, je me pose la question suivante :

  1. Quelqu'un peut-il m'indiquer de bonnes sources détaillées à partir desquelles je pourrais comment implémenter des tests dans mon application MVC 3, en utilisant NUnit, Ninject 2 et Moq ? NUnit, Ninject 2 et Moq ?
  2. Quelqu'un peut-il m'aider à comprendre comment Controller-Repository le découplage, le mocking, et l'injection de dépendances fonctionnent ensemble ?

EDIT :

Je viens de découvrir le Wiki officiel de Ninject sur Github, je vais donc commencer à travailler dessus pour voir si cela commence à clarifier les choses pour moi. Mais je suis toujours très intéressé par les réflexions de la communauté SO sur tout cela :)

60voto

Chris Sainty Points 5391

Si vous utilisez le Ninject.MVC3 nuget, alors une partie de l'article que vous avez mis en lien et qui causait de la confusion ne sera pas nécessaire. Ce paquetage contient tout ce dont vous avez besoin pour commencer à injecter vos contrôleurs, ce qui est probablement le plus gros problème.

Lors de l'installation de ce paquet, un fichier NinjectMVC3.cs est créé dans le dossier App_Start, à l'intérieur duquel se trouve une méthode RegisterServices. C'est ici que vous devez créer les liens entre vos interfaces et vos implémentations.

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

Dans votre contrôleur, vous pouvez maintenant utiliser l'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 recherchez une couverture de test très élevée, alors à chaque fois qu'un morceau de code logique (par exemple un contrôleur) doit communiquer avec un autre (par exemple une base de données), vous devez créer une interface et une implémentation, ajouter la définition de la liaison à RegisterService et ajouter un nouvel argument au constructeur.

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

Ensuite, lorsqu'il s'agit de tester, ce que vous voulez faire, c'est fournir une version simulée de toutes les interfaces requises, de sorte que la seule chose que vous testez est le code de la méthode pour laquelle vous écrivez le test. Dans mon exemple, disons que IRepository a une interface

bool TryCreateUser(string username);

Qui est appelé par une méthode du 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 l'instruction if et les types de retour. Vous ne voulez pas avoir à créer un véritable référentiel qui renverra vrai ou faux en fonction des valeurs spéciales que vous lui donnerez. 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 compilera pas pour vous car je connais mieux xUnit et je ne me souviens pas des noms des propriétés de RedirectToActionResult.

En résumé, si vous voulez qu'un morceau de code parle à un autre, ajoutez une interface entre les deux. Cela vous permet ensuite de simuler le second morceau de code de sorte que lorsque vous testez le premier, vous pouvez contrôler la sortie et être sûr que vous ne testez que le code en question.
Je pense que c'est ce point qui m'a vraiment fait comprendre que l'on ne fait pas nécessairement cela parce que le code l'exige, mais parce que les tests l'exigent.

Un dernier conseil spécifique à MVC, chaque fois que vous avez besoin d'accéder aux objets web de base, HttpContext, HttpRequest, etc., enveloppez-les derrière une interface (comme IWebData dans mon exemple) parce que même si vous pouvez simuler ces objets en utilisant les classes *Base, cela devient très vite pénible car elles ont beaucoup de dépendances internes que vous devez également simuler.
De même, avec Moq, mettez le MockBehaviour à Strict lorsque vous créez des mocks et il vous dira si quelque chose est appelé pour lequel vous n'avez pas fourni de mock.

9voto

alexanderb Points 6553
  1. Voici l'application que je suis en train de créer. Elle est open source et disponible sur github, et utilise tous les éléments nécessaires - MVC3, NUnit, Moq, Ninject -. https://github.com/alexanderbeletsky/trackyt.net/tree/master/src

  2. Le découplage entre le conteneur et le dépôt est simple. Toutes les opérations sur les données sont déplacées vers le référentiel. Le référentiel est une implémentation d'un type IRepository. Le contrôleur ne crée jamais de référentiel à l'intérieur de lui-même (avec l'option new ) mais les reçoit soit par un argument du constructeur, soit par une propriété.

.

public class HomeController {
  public HomeController (IUserRepository users) {

  }
}

Cette technique est appelée "inversion du contrôle". Pour prendre en charge l'inversion de contrôle, vous devez fournir un cadre d'"injection de dépendance". Ninject en est un bon exemple. Dans Ninject, vous associez une interface particulière à une classe d'implémentation :

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

Vous pouvez également remplacer la fabrique de contrôleurs par défaut par votre propre fabrique. Dans la fabrique personnalisée, 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 GetControllerInstance de la fabrique de contrôleurs personnalisée, qui la délègue à 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, il voit que "je dois créer un UserRepository pour satisfaire le besoin IUserRepository". Il crée l'instance et la passe au constructeur.

Le constructeur n'est jamais au courant de l'instance exacte qui lui sera transmise. Tout dépend de la liaison que vous fournissez pour cela.

Exemples de codes :

1voto

Vincent Points 271

Vérifier : DDD Melbourne video - Nouveau processus de développement

L'ensemble du processus de développement ASP.NET MVC 3 a été très bien présenté.

Les outils tiers que j'apprécie le plus sont les suivants :

  • Utilisation de NuGet pour installer Ninject afin d'activer la DI dans l'ensemble du système MVC3 et de l'ensemble de l'architecture
  • Utilisation de NuGet pour installer nSubstite afin de créer des mocks pour activer l'unité unitaires

1voto

lloydphillips Points 1189

Je vous conseille de consulter www.tekpub.com - Rob Connery utilise Ninject et Moq dans sa nouvelle série MVC3 et ils sont fantastiques pour l'apprentissage.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X