cam*_*elt 48 nunit unit-testing moq ninject-2 asp.net-mvc-3
我的问题的简短版本:
我的问题的更长版本:
我想做什么......
我目前正在开始创建一个MVC 3应用程序,它将使用Entity Framework 4,使用数据库第一种方法.我想这样做,所以我试图设计类,层等,以便高度可测试.但是,除了对它们的学术理解之外,我对单元测试或集成测试几乎没有经验.
经过大量的研究,我决定使用
我知道哪个框架最好的主题,等等,可以进入这个,但在这一点上,我真的不知道任何一个形成一个坚实的意见.所以,我决定采用这些免费的解决方案,这些解决方案似乎很受欢迎并且维护得很好.
到目前为止我学到了什么......
我花了一些时间研究这些东西,阅读资源,如:
从这些资源中,我已经设法满足了对存储库模式的需求,以及存储库接口,以便分离我的控制器和数据访问逻辑.我已经在我的应用程序中写了一些内容,但我承认我不清楚整个事情的机制,以及我是否正在进行这种解耦以支持模拟或依赖注入,或两者兼而有之.因此,我当然不介意听到你这些人的意见.在这一点上我可以获得的任何清晰度都会对我有所帮助.
对我来说事情变得浑浊......
在我开始尝试绕过Ninject之前,我认为我已经很好地掌握了这些东西,如上面引用的构建可测试的ASP.NET MVC应用程序中所述.具体来说,我完全迷失了作者开始描述服务层实现的程度,大约是文档的一半.
无论如何,我现在正在寻找更多的资源来学习,以便尝试围绕这些东西获得各种观点,直到它开始对我有意义.
总结所有这些,将其归结为具体问题,我想知道以下内容:
编辑:
我刚刚在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,它将告诉您是否正在调用任何未提供模拟的内容.
这是我正在创建的应用程序.它是开源的,可以在github上使用,并使用所有必需的东西 - MVC3,NUnit,Moq,Ninject - https://github.com/alexanderbeletsky/trackyt.net/tree/master/src
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的需求." 它创建实例并将其传递给构造函数.
构造函数永远不会知道内部传递的确切实例.这完全取决于您为此提供的绑定.
代码示例: