我们知道,由于HttpContext很难进行Mock,因此为了提高可测试性,微软随ASP.NET MVC发布了一个“抽象包”,专门用于对HttpContext及其相关组件进行抽象。不过在Preview 1版本中,这些抽象都是一个个接口,如IHttpContext,IHttpRequest等等。而在下一个版本中,立即就成为了一个个抽象类,如HttpContextBase,HttpRequestBase。从命名上来说,我喜欢接口的命名方式,清晰而好用。而在自己的项目中,我也倾向于使用接口而不是抽象类。但是对于ASP.NET Abstractions中的这些抽象类型,我倒觉得“的确应该如此”。除了在公开API中应该谨慎地使用接口这一原因(而对于HttpContext这种拥有数十个成员的类型更不应该使用接口),有些朋友也提到HttpContext是一个实际的概念,而接口表示的是“can do”,因此应该使用抽象类。不过,现在我打算从“使用”角度来谈一下,为什么这里的确应该用抽象类而不是接口。
关于这点,我们应该直接来看HttpContextBase这个抽象类的写法:
public abstract class HttpContextBase : IServiceProvider
{
protected HttpContextBase() { }
public virtual void AddError(Exception errorInfo)
{
throw new NotImplementedException();
}
public virtual void ClearError()
{
throw new NotImplementedException();
}
...
}
我们可以看到,虽然HttpContextBase是抽象类,但是其中没有任何一个成员是抽象的,只不过每个成员都会直接抛出NotImplementedException。为什么这样?
这是由这个类的作用决定的。ASP.NET Abstractions中的类都是为了提高“可测试性”,而是用手段就是提供一个“抽象”以便创建Mock对象。一个Mock对象的作用,简单地说便是模拟真实对象的行为,然后交给被测试功能使用,以此判断被测试功能是否正确。例如,我们使用Moq框架可以轻松地创建HttpContext的Mock对象:
var mockContext = new Mock<HttpContextBase>();
mockContext.Setup(c => c.Request.Form).Returns(new NameValueCollection());
DoSomething(mockContext.Object);
由于DoSomething基于HttpContext的抽象HttpContextBase编写,因此我们可以准备一个Mock对象,并使它的Request.Form属性返回我们需要的内容,这样DoSomething方法内部就可以访问到我们所期望的值了。但实际上,Moq等Mock框架的工作方式,都是动态生成一些类型,继承抽象类(或实现接口),并根据我们的“设置”来实现一些成员。因此,它们基本上都要对被Mock的对象有一定要求,例如不允许是一个密闭(sealed)类,而被操作的成员也必须是abstract或virtual的,因为只有这样,动态类型才能替换掉这些成员的实现1。
显然,HttpContextBase满足这个要求。事实上,Mock框架只是帮我们省去了编写下面这些代码的负担(当然写法可以不同):
public class MockHttpContext : HttpContextBase
{
public override HttpRequestBase Request
{
get
{
return this.MockRequest;
}
}
public HttpRequestBase MockRequest { get; set; }
}
public class MockHttpRequest : HttpRequestBase
{
public override NameValueCollection Form
{
get
{
return this.MockForm;
}
}
public NameValueCollection MockForm { get; set; }
}
试想,如果HttpContextBase是IHttpContext接口怎么办?没错,除了我们需要的成员之外,我们还必须准备一大堆我们不需要的成员的“空架子”——同样道理,如果拥有抽象的成员也会遇到这个情况。因此,虽然HttpContextBase是抽象类,但是没有任何一个抽象成员。这意味着当我们需要手动实现Mock对象的时候,只需要覆盖我们的目标成员就可以了。