快捷搜索:

尽可能摆脱对HttpContext的依赖

在ASP.NET中进行单元测试的天敌就是HttpContext,它是ASP.NET的核心,极度繁杂,却无法进行Mock1——可见微软能够写出那么宏大年夜的ASP.NET框架真不那么轻易。现在这个状况改良了不少,是以大年夜家已经可以应用System.Web.Abstractions.dll了,这个法度榜样集中供给了对付HttpContext的抽象,也便是HttpContextBase抽象类。是以在ASP.NET MVC中,各类组件均依附于HttpContextBase而不是HttpContext。这是一个优秀的做法,大年夜家今后可以尽可能地开脱HttpContext了。

不过这彷佛又是一个悖论。虽然已经可以对HttpContext进行Mock(这点增强了可测试性),然则过度依附HttpContext对付单元测试来说也是一个危害。这是HttpContext工具的天性所致:它其实太繁杂了。您应该已经察觉到,这是个集万千痛爱于一身的工具,从哀求,回覆,利用法度榜样,缓存……险些包孕了Web利用法度榜样必要的所有信息。假如要测试一个依附于HttpContext的措施,您势需要为HttpContext的Mock工具添补各类信息——其繁杂程度视营业而定。而且,Mock关注的是“行径”,也便是说它关注的是做一件工作所应用“路径”。那么假如做一件工作可以采纳多个路径又会如何?是否必要在测试之前筹备好所有的路径,并且验证被测试的代码“采纳了,并仅仅采纳了此中一条路径”?是以,Stub逐步进入人们的视线。Stub关注的是“状态”……这便是另一个话题了,还会涉及到采纳Record & Replay照样Arrange-Act-Assert要领来进行单元测试,暂且不提。

之前谈到对视图进行单元测试时,老赵曾经谈起在视图中应该只应用ViewData中的数据。这不是第一次提及要放弃HttpContext了,自从有了“抽象”这一有利武器后,统统“反面谐”身分都能够被分离。试想在MVP模式中,View和Presenter都应用各自的抽象进行交互,统统Web控件,HttpContext等工具都不复存在了,大年夜家眼中只有“数据”和“模型”。同样,在ASP.NET MVC的Action措施中,也不应该应用HttpContext,这是基于优越的“可测试性”而斟酌的。您可能会想,现在的HttpContextBase工具已经可以Mock了啊。没错,它切实着实“可以”,然则这样做会引起单元测试代码的膨胀,由于测试代码中的相称部分必须关注在测试数据的筹备,而不是被测试的功能上。对付一个Action措施来说,它关注的应该是用户与营业逻辑的交互,而不是“若何把HTTP哀求转化为可用的数据”。着实说到底,照样要“分离关注点”。

在ASP.NET MVC中认真“转化数据”的层次为Model Binder。关于这一点,现有的“示例”大年夜都关注把Form或QueryString中的数据转化为Action参数上,不过Model Binder可用的地方着实更多。例如在《最佳实践》的代码中,蓝本AccountController的Delete措施实现如下:

public ActionResult Delete(string userName)

{

this.MiddleTier.UserManager.Delete(userName);

Uri urlReferrer = this.Request.UrlReferrer;

return this.Redirect(urlReferrer.ToString());

}

在删除了指定工具之后,页面将跳转到Url Referrer地址中。在上面的代码中,这个值将经由过程造访Request.UrlReferer来得到。这就使您的Action措施与HttpContext孕育发生了依附,是以它的单元测试代码就必要这样编写:

[TestMethod]

public void DeleteTest()

{

string userName = "jeffz";

Uri urlReferrer = new Uri("http://www.microsoft.com");

var mockHttpContext = new Mock();

mockHttpContext.Setup(c => c.Request.UrlReferrer).Returns(urlReferrer);

var mockController = this.GetMockController();

mockController.Setup(c => c.MiddleTier.UserManager.Delete(userName)).Verifiable();

mockController.Object.ControllerContext = new ControllerContext(

mockHttpContext.Object, new RouteData(), mockController.Object);

mockController.Object.Delete(userName)...

}

回到正题。假如要让Delete措施接urlReferrer受参数,那么我们就要编写Model Binder相关的组件:

public class UrlReferrerModelBinder : IModelBinder

{

public object BindModel(

ControllerContext controllerContext,

ModelBindingContext bindingContext)

{

return controllerContext.HttpContext.Request.UrlReferrer;

}

}

并使其可以直接运用到Action的参数上:

public class UrlReferrerAttribute : CustomModelBinderAttribute

{

private static UrlReferrerModelBinder s_modelBinder =

new UrlReferrerModelBinder();

public override IModelBinder GetBinder()

{

return s_modelBinder;

}

}

于是乎,我们的Delete措施便可写为:

public ActionResult Delete(string userName, Uri urlReferrer)

{

this.MiddleTier.UserManager.Delete(userName);

return this.Redirect(urlReferrer.ToString());

}

如今的代码,无论是利用法度榜样照样框架类库,都必须斟酌“可测试性”这个要求。例如.NET 3.0的WF,因为其可测试性不佳不停为人所诟病。现在我们在编写法度榜样时,要时候扣问自己:“这么做方便测试吗?”斟酌到这个问题,可能您就会宁神地做出某些决定了2。

注1:着实照样可以Mock的。例如Typemock应用Profiler的要领进行直接注入,可以Mock任何成员。不过,假如Moq等框架无法满意您的必要,一样平常就是您的设计有些问题了。

注2:例如,究竟让Action措施返回ActionResult,照样返回void,并直接经由过程Response输出呢?

您可能还会对下面的文章感兴趣: