MVC 3:如何学习如何使用NUnit,Ninject和Moq进行测试?

cam*_*elt 48 nunit unit-testing moq ninject-2 asp.net-mvc-3

我的问题的简短版本:

  1. 任何人都可以指向一些好的,详细的资源,我可以从中学习如何使用NUnit,Ninject 2和Moq在我的MVC 3应用程序中实现测试?
  2. 这里的任何人都可以帮我澄清一下Controller-Repository解耦,模拟和依赖注入是如何协同工作的吗?

我的问题的更长版本:

我想做什么......

我目前正在开始创建一个MVC 3应用程序,它将使用Entity Framework 4,使用数据库第一种方法.我想这样做,所以我试图设计类,层等,以便高度可测试.但是,除了对它们的学术理解之外,我对单元测试或集成测试几乎没有经验.

经过大量的研究,我决定使用

  • NUnit作为我的测试框架
  • Ninject 2作为我的依赖注入框架
  • Moq是我的嘲弄框架.

我知道哪个框架最好的主题,等等,可以进入这个,但在这一点上,我真的不知道任何一个形成一个坚实的意见.所以,我决定采用这些免费的解决方案,这些解决方案似乎很受欢迎并且维护得很好.

到目前为止我学到了什么......

我花了一些时间研究这些东西,阅读资源,如:

从这些资源中,我已经设法满足了对存储库模式的需求,以及存储库接口,以便分离我的控制器和数据访问逻辑.我已经在我的应用程序中写了一些内容,但我承认我不清楚整个事情的机制,以及我是否正在进行这种解耦以支持模拟或依赖注入,或两者兼而有之.因此,我当然不介意听到你这些人的意见.在这一点上我可以获得的任何清晰度都会对我有所帮助.

对我来说事情变得浑浊......

在我开始尝试绕过Ninject之前,我认为我已经很好地掌握了这些东西,如上面引用的构建可测试的ASP.NET MVC应用程序中所述.具体来说,我完全迷失了作者开始描述服务层实现的程度,大约是文档的一半.

无论如何,我现在正在寻找更多的资源来学习,以便尝试围绕这些东西获得各种观点,直到它开始对我有意义.

总结所有这些,将其归结为具体问题,我想知道以下内容:

  1. 任何人都可以指向一些好的,详细的资源,我可以从中学习如何使用NUnit,Ninject 2和Moq在我的MVC 3应用程序中实现测试?
  2. 这里的任何人都可以帮我澄清一下Controller-Repository解耦,模拟和依赖注入是如何协同工作的吗?

编辑:

我刚刚在Github上发现了Ninject官方维基,所以我将开始研究它是否开始为我澄清事情.但是,我仍然对SO社区对所有这些的想法非常感兴趣:)

Chr*_*nty 60

如果您使用的是Ninject.MVC3 nuget包,则不需要您链接的一些导致混淆的文章.该软件包具有开始注入控制器所需的一切,这可能是最大的痛点.

安装该软件包后,它将在App_Start文件夹中创建一个NinjectMVC3.cs文件,该类内部是RegisterServices方法.这是您应该在接口和实现之间创建绑定的地方

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

现在在您的控制器中,您可以使用构造函数注入.

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

    public HomeController(IRepository repo, IWebData webData) {
      _Repo = repo;
      _WebData = webData;
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您的测试覆盖率非常高,那么基本上任何时候一个逻辑代码(比如控制器)需要与另一个代码(比如数据库)交谈,您应该创建一个接口和实现,将定义绑定添加到RegisterService并添加一个新的构造函数参数.

这不仅适用于Controller,也适用于任何类,因此在上面的示例中,如果您的存储库实现需要某个WebData实例,您可以将readonly字段和构造函数添加到您的存储库实现中.

然后,当涉及到测试时,您要做的是提供所有必需接口的模拟版本,因此您唯一要测试的是您编写测试的方法中的代码.所以在我的例子中,说IRepository有一个

bool TryCreateUser(string username);
Run Code Online (Sandbox Code Playgroud)

这是由控制器方法调用的

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

你真正想要测试的是if语句和返回类型,你不想创建一个真实的存储库,它将根据你给它的特殊值返回true或false.这是你想要模拟的地方.

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"]);
}
Run Code Online (Sandbox Code Playgroud)

^那不会为你编译,因为我更了解xUnit,并且不记得我头顶的RedirectToActionResult上的属性名称.

总而言之,如果你想让一段代码与另一段代码交谈,那么在它们之间敲击一个接口.然后,这允许您模拟第二段代码,这样当您测试第一段代码时,您可以控制输出并确保您只测试相关代码.
我认为正是这一点确实让我的所有这一切都下降了,你这样做不一定是因为代码要求它,而是因为测试需要它.

有关MVC的最后一条建议,任何时候你需要访问基本的Web对象,HttpContext,HttpRequest等,将所有这些包装在一个接口后面(就像我的例子中的IWebData),因为虽然你可以使用*来模拟这些基类,它很快就会变得很痛苦,因为它们还需要模拟很多内部依赖项.
同时使用Moq,在创建模拟时将MockBehaviour设置为Strict,它将告诉您是否正在调用任何未提供模拟的内容.

  • 克里斯,这是一个很棒的答案.您的描述和示例清晰简洁.你在一篇文章中告诉我的比我从4篇文章中学到的更多.谢谢!但是,我确实有一个(一种愚蠢的)问题.我应该在我的测试项目或我正在测试的项目中安装Ninject,还是两者都安装?我只是在这一点上感到困惑,因为我读到了某个地方,在MVC3扩展之前,-all-控制器需要由DI框架提供,我认为这意味着我的测试项目中的所有控制器,但只是想确保. (2认同)
  • @campbell您应该只需要在您的Web项目中使用Ninject.可以在你的测试项目中使用它来为你注入新的Mock <T>,但我更喜欢手工完成. (2认同)

Ale*_*sky 9

  1. 这是我正在创建的应用程序.它是开源的,可以在github上使用,并使用所有必需的东西 - MVC3,NUnit,Moq,Ninject - https://github.com/alexanderbeletsky/trackyt.net/tree/master/src

  2. Contoller-Repository解耦很简单.所有数据操作都将移至存储库.存储库是某种IRepository类型的实现.控制器永远不会在其自身内部(使用new运算符)创建存储库,而是通过构造函数参数或属性接收它们.

.

public class HomeController {
  public HomeController (IUserRepository users) {

  }
}
Run Code Online (Sandbox Code Playgroud)

这种技术被称为"控制反转".要支持控制反转,您必须提供一些"依赖注入"框架.Ninject是一个很好的.在Ninject内部,您将某个特定接口与实现类相关联:

Bind<IUserRepository>().To<UserRepository>();
Run Code Online (Sandbox Code Playgroud)

您还可以使用自定义控制器替换默认控制器工厂.在自定义内部,您将调用委托给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;
    }
}
Run Code Online (Sandbox Code Playgroud)

当MVC基础结构即将创建新控制器时,该调用将委派给自定义控制器工厂GetControllerInstance方法,该方法将其委托给Ninject.Ninject认为,要创建该控制器,构造函数有一个类型的参数IUserRepository.通过使用声明的绑定,它看到"我需要创建一个UserRepository来满足IUserRepository的需求." 它创建实例并将其传递给构造函数.

构造函数永远不会知道内部传递的确切实例.这完全取决于您为此提供的绑定.

代码示例: