在C#单元测试中使用HttpContext的简单解决办法

场景:最近在测试一个.NET的Http Module,这个Module是用来做URL重写的。刚开始进展的比较顺利,因为该Module里面的方法参数基本上都是String,后来这个Module进行了一下重构,所有参数都变成了HttpContext了,这就直接导致原来的单元测试都跑不起来了,接着就开始了弄HttpContext了。

1. 采用Visual Studio自带的ASP.NET单元测试

刚开始我看了一下被测试的代码,虽然说用到了HttpContext,但是有很多地方我都可以绕过去的,意思就是这个HttpContext只是名以上需要的一个参数,只要它不是NULL就可以了,并不影响我的测试,所以我采用了ASP.NET Unit Test的办法来获取一个HttpContext,这个方法实现起来是最简单的,但是会有一些问题,后面会提及到。

首先新建一个WEB项目,然后把被测的Http Module挂到这个新建的WEB项目中。然后就可以配置ASP.NET单元测试,把原来一般的单元测试改造成为一个ASP.NET的单元测试。步骤如下,在测试方法前面加上以下属性

[HostType("ASP.NET")]
[UrlToTest("http://localhost:6988/Default.aspx")]
[AspNetDevelopmentServerHost("D:\\MS_Code\\Public\\UrlRouter.WebTest", "/")]

[UrlToTest] — 这个属性指定了运行该单元测试时的URL

[HostType] — 一般的单元测试是在VSTest的宿主进程下运行,所以是没有HttpContext的;如果作为ASP.NET单元测试,那么必须要在ASP.NET宿主进程下运行

[AspNetDevelopmentServerHost] — 由于我使用了ASP.NET Development Server 作为测试的主机服务器,而不是IIS;所以我需要设置这个属性来指定Web应用程序的完整路径。这里我需要把目录指向刚才新建的WEB项目的路径中。这样子就可以在单元测试中直接使用HttpContext.Current来获取一个HttpContext了。

[[TestMethod]
[HostType("ASP.NET")]
[AspNetDevelopmentServerHost("D:\\MS_Code\\Public\\UrlRouter.WebTest", "/")]
[UrlToTest("http://localhost:6988/Default.aspx")]
public void Fuseaction_PUT()
{
    handler = new UrlMapHandler(HttpContext.Current);
    Assert.IsNotNull(handler);
    result = handler.ExecuteUrlMap();
    StringAssert.Contains(result.Url, "expected string");
}

这样子初步解决了需要HttpContext的困难,但是会有以下问题:

  1. 对HttpContext的内容不可控
  2. 需要运行ASP.NET Development Server
  3. 不能进行调试

2. 直接创建HttpContext实例

基于刚才提到的3个不足,我决定直接创建一个HttpContext来解决问题。还好,HttpContext有公开的构造函数,这个构造函数需要接受一个HttpWorkerRequest作为参数,HttpWorkerRequest是一个抽象类,就是不能直接对之实例化啦,不过好消息是,微软有一个简单的类“SimpleWorkerRequest”,这个类实现了HttpWorkerRequest类;下面是一段简单的代码

TextWriter tw = new StringWriter();
HttpWorkerRequest wr = new SimpleWorkerRequest
("default.aspx", "friendId=1300000000", tw);
HttpContext.Current = new HttpContext(wr);

这样子就能在单元测试里面用到HttpContext了,好处有:

  1. 可以控制HttpContext的内容了,对于URL重写这部分来说,会比较关心请求路径,还有queryString,这两个可以在实例化SimpleWorkerRequest的时候,作为参数传递进去。
  2. 不需要ASP.NET Development Server。这个很重要,因为可以满足一个自包含(self-contained)的单元测试的要求
  3. 可以调试

3.改良后的方案

到此,世界很美好,测试进行的很顺利。好日子没过多久,我又遇到一个问题,这个URL重写模块,还应用了一个规则引擎,其中进行了大量对HOST判断的操作,很不幸,用刚才的方法创建的HttpContext,它返回的Request.Url中,HOST永远都是127.0.0.1,很郁闷。这时候,一个所有做.NET的人都需要的屠龙刀派上用场了!.NET Reflector,用Reflector打开System.Web.dll,找到System.Web.Hosting这个命名空间,然后找到SimpleWorkerRequest这个类,发现里面的GetLocalAddress()方法,发现里面就是Hard-Code了一个地址:127.0.0.1;好了,知道问题在哪里了,着手写一个新的类,这个类继承SimpleWorkerRequest,然后重写他的GetLocalAddress()方法:

public class MyWorkerRequest : SimpleWorkerRequest
{
    private string localAdd = string.Empty;

    public MyWorkerRequest(string page, string query, TextWriter output, string address)
        : base(page, query, output)
    {
        this.localAdd = address;
    }

    public override string GetLocalAddress()
    {
        return this.localAdd;
    }
}

这样子,实例化HttpContext的时候就是这样子:

Thread.GetDomain().SetData(".appPath", "c:\\inetpub\\wwwroot\\webapp\\");
Thread.GetDomain().SetData(".appVPath", "/");
TextWriter tw = new StringWriter();
String address = "home.myspace.cn";
HttpWorkerRequest wr = new MyWorkerRequest
("default.aspx", "friendId=1300000000", tw, address);
HttpContext.Current = new HttpContext(wr);

需要注意的是,对于.appPath和.appVPath的设置是必须的,因为在SimpleWorkerRequest的构造函数中,会取这两个数值。
经过这样的改造,基本上已经满足了测试的需求了。

总结一下:

  1. 这个事情居然用了1天多时间,后来发现解决的办法就在我订阅的博客里面就有,以后遇到什么难题先找一个Google Reader。
  2. 多用.NET Reflector,Visual Studio经常让你看meta data,那些meta data对我们来说一点用处都没有,还是Reflector好啊。

1 thought on “在C#单元测试中使用HttpContext的简单解决办法”

Leave a Reply

Your email address will not be published. Required fields are marked *