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

我的问题的简短版本:

  1. 任何人都可以指向我一些很好的,详细的来源,我可以学习如何使用NUnit,Ninject 2和Moq在我的MVC 3应用程序中实现testing吗?
  2. 任何人都可以帮助我澄清Controller-Repository解耦,嘲笑和dependency injection是如何协同工作的?

我的问题的更长版本:

我在做什么

我目前正在开始创build一个MVC 3应用程序,它将使用entity framework4,采用数据库优先的方法。 我想这样做是正确的,所以我试图devise类,图层等,是高度可testing的。 但是,对于unit testing或集成testing,除了对其的理解之外,我几乎没有任何经验。

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

  • NUnit作为我的testing框架
  • Ninject 2作为我的dependency injection框架
  • Moq作为我的嘲笑框架。

我知道哪个框架是最好的,等等,可以进入这个话题,但是在这一点上,我真的不知道它是否足以形成一个坚实的观点。 所以,我只是决定采取这些免费的解决scheme,似乎很受欢迎,维护良好。

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

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

  • 在ASP.NET MVC应用程序中实现工作模式的存储库和单元
  • 构build可testing的ASP.NET MVC应用程序
  • NerdDinner第12步:unit testing
  • 在Entity Framework 4.0中使用存储库和工作单元模式

从这些资源中,我已经设法解决了需要存储库模式的问题,这些模式需要存储库接口,以解耦我的控制器和数据访问逻辑。 我已经将这些写入了我的应用程序,但是我承认我并不清楚整个系统的机制,我是否正在为支持嘲弄或dependency injection而进行这种解耦。 因此,我当然不会介意从你们这里听到这个。 任何澄清,我可以得到这个东西将帮助我在这一点上。

事情变得泥泞的地方

我以为我很好地掌握了这个东西,直到我开始尝试围绕Ninject进行打包,正如上面引用的“ 构build可testing的ASP.NET MVC应用程序”中所述。 具体来说,在作者开始描述服务层的实现的时候,我已经完全迷失了,大约在文档的一半。

无论如何,我现在正在寻找更多的资源来学习,试图对这个东西有不同的看法,直到它开始对我有意义。

总结所有这一切,把它归结为具体的问题,我想知道以下几点:

  1. 任何人都可以指向我一些很好的,详细的来源,我可以学习如何使用NUnit,Ninject 2和Moq在我的MVC 3应用程序中实现testing吗?
  2. 任何人都可以帮助我澄清Controller-Repository解耦,嘲笑和dependency injection是如何协同工作的?

编辑:

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

如果您使用的是Ninject.MVC3 nuget包,那么您链接的那些导致混淆的文章将不再需要。 该软件包包含了开始注入控制器所需的一切,这可能是最大的难题。

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

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

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

 public class HomeController : Controller { private readonly IRepository _Repo; private readonly IWebData _WebData; public HomeController(IRepository repo, IWebData webData) { _Repo = repo; _WebData = webData; } } 

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

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

然后当涉及到testing时,你想要做的是提供所有需要的接口的模拟版本,所以你唯一要testing的是你正在编写testing的方法中的代码。 所以在我的例子中,说IRepository有一个

 bool TryCreateUser(string username); 

这是由控制器方法调用

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

你真正要在这里testing的是,如果语句和返回types,你不想创build一个真正的存储库,将根据你给它的特殊值返回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"]); } 

^这不会为你编译,因为我知道xUnit更好,不记得在我头上的RedirectToActionResult的属性名称。

所以总结一下,如果你想要一段代码与另一段代码交谈,可以在两者之间敲一个接口。 然后,这可以让你嘲笑第二段代码,这样当你testing第一段代码时,你可以控制输出,并确定你只testing有问题的代码。
我认为这一点对于我来说真的是一分钱一滴,你这样做不一定代码需要它,而是因为testing需要它。

最后一个特定于MVC的build议,任何时候你需要访问基本的Web对象,HttpContext,HttpRequest等等,把所有这些都包含在一个接口之后(比如我的例子中的IWebData),因为虽然你可以使用*基类,它很快变得痛苦,因为他们有很多内部的依赖,你也需要嘲笑。
另外在Moq中,创build模拟时将MockBehaviour设置为Strict,它会告诉你是否有任何东西被调用,而你没有提供模拟。

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

  2. 控制器 – 存储库解耦是简单的。 所有的数据操作都将移向Repository。 存储库是一些IRepositorytypes的实现。 控制器从来不会在自身内部创build存储库(使用new运算符),而是通过构造函数参数或属性接收它们。

 public class HomeController { public HomeController (IUserRepository users) { } } 

这种技术被称为“控制反转”。 为了支持控制反转,你必须提供一些“dependency injection”框架。 Ninject是一个很好的。 在Ninject里面,你将一些特定的接口与一个实现类联系起来:

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

您也可以使用您的自定义控制器工厂来替代默认控制器 在自定义函数中,您将调用委托给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; } } 

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

构造函数永远不会知道究竟会传入哪个实例。 这一切都取决于你提供的绑定。

代码示例:

检查出来:DDD墨尔本video – 新的开发工作stream程

整个ASP.NET MVC 3开发过程非常好。

我最喜欢的第三方工具是:

  • 使用NuGet安装Ninject以在整个MVC3框架中启用DI
  • 使用NuGet安装nSubstite来创build模拟以启用unit testing