• 走进单元测试(3):消灭HttpContext的依赖,兼谈单元测试的设计辅助性


    前篇提到过由于我们已经有了一个现成的平台,现在要对其进行单元测试的补完。而在这个过程中,就出现了HttpContext这类东西,其依附于一个host环境,对单元测试的自动化是一个很大的阻碍。

    对于HttpContext,如果没有一个web托管环境,其中的Request和Response等只读属性根本就无法造出来。而如果要搭建一个web托管环境,不仅为测试带来了干扰(因为要确定是否是托管环境的问题),而且给测试的自动化带来了不方便。那么怎么去解决这个问题呢?

    在MSDN中我们可以查到一个叫SimpleWorkerRequest的东西,这个东西的提供了一个简单的System.Web.HttpWorkerRequest的实现,使得我们可以在IIS之外托管ASP.NET应用程序。而当我们使用reflector来查看这个东西的源码的时候,发现其中的一些方法很有趣:

    SimpleWorkerRequest
     1 [ComVisible(false), AspNetHostingPermission(SecurityAction.InheritanceDemand, Level=AspNetHostingPermissionLevel.Minimal), AspNetHostingPermission(SecurityAction.LinkDemand, Level=AspNetHostingPermissionLevel.Minimal)]
     2 public class SimpleWorkerRequest : HttpWorkerRequest
     3 {
     4     //...
     5 
     6     public override string GetHttpVerbName()
     7     {
     8         return "GET";
     9     }
    10 
    11     public override string GetHttpVersion()
    12     {
    13         return "HTTP/1.0";
    14     }
    15 
    16     public override string GetLocalAddress()
    17     {
    18         return "127.0.0.1";
    19     }
    20 
    21     public override int GetLocalPort()
    22     {
    23         return 80;
    24     } 
    25 
    26     //...
    27 }

    这果然是一个简单的实现啊,把IP地址,Http版本,端口等全部硬编码了。

    但对我们的需求来说,其实也非常的简单,既然他硬编码了,那我们再派生一下,把这些方法override一下不就可以了:

    TestWorkerRequest
     1     /// <summary>
     2     /// Provides a simple implementation of the System.Web.HttpWorkerRequest abstract class that can be used to host ASP.NET applications outside an Internet Information Services (IIS) application. 
     3     /// This class can be used for unit test which needs a web host.
     4     /// </summary>
     5     public class TestWorkerRequest : SimpleWorkerRequest
     6     {
     7         private readonly string hostName = "";
     8 
     9         /// <summary>
    10         /// Initializes a new instance of the <see cref="T:System.Web.Hosting.SimpleWorkerRequest"/> class when the target application domain has been created using the <see cref="M:System.Web.Hosting.ApplicationHost.CreateApplicationHost(System.Type,System.String,System.String)"/> method.
    11         /// </summary>
    12         /// <param name="page">The page to be requested (or the virtual path to the page, relative to the application directory).</param>
    13         /// <param name="query">The text of the query string.</param>
    14         /// <param name="output"><see cref="T:System.IO.TextWriter"/> that captures output from the response</param>
    15         /// <param name="hostName">The host name that will be requested.</param>
    16         public TestWorkerRequest(string page, string query, TextWriter output, string hostName) 
    17             : base(page, query, output)
    18         {
    19             this.hostName = hostName;
    20         }
    21 
    22         /// <summary>
    23         /// Initializes a new instance of the <see cref="T:System.Web.Hosting.SimpleWorkerRequest"/> class for use in an arbitrary application domain, when the user code creates an <see cref="T:System.Web.HttpContext"/> (passing the SimpleWorkerRequest as an argument to the HttpContext constructor).
    24         /// </summary>
    25         /// <param name="appVirtualDir">The virtual path to the application directory; for example, "/app".</param>
    26         /// <param name="appPhysicalDir">The physical path to the application directory; for example, "c:\app".</param>
    27         /// <param name="page">The virtual path for the request (relative to the application directory).</param>
    28         /// <param name="query">The text of the query string.</param>
    29         /// <param name="output"><see cref="T:System.IO.TextWriter"/> that captures the output from the response.</param>
    30         /// <param name="hostName">The host name that will be requested.</param>
    31         /// <exception cref="T:System.Web.HttpException">The <paramref name="appVirtualDir"/> parameter cannot be overridden in this context.
    32         /// </exception>
    33         public TestWorkerRequest(string appVirtualDir, string appPhysicalDir, string page, string query, TextWriter output, string hostName) 
    34             : base(appVirtualDir, appPhysicalDir, page, query, output)
    35         {
    36             this.hostName = hostName;
    37         }
    38 
    39         /// <summary>
    40         /// Returns the server IP address of the interface on which the request was received.
    41         /// </summary>
    42         /// <returns>
    43         /// The server IP address of the interface on which the request was received.
    44         /// </returns>
    45         public override string GetLocalAddress()
    46         {
    47             return hostName;
    48         }
    49     }

    那么使用如下代码便可以模拟出一个HttpContext了,这样依赖就不存在了。

    代码
                //设置Cookie环境
                 Thread.GetDomain().SetData(".appPath"@"D:\Test");
                Thread.GetDomain().SetData(
    ".appVPath""/");
                TextWriter tw 
    = new StringWriter();
                
    string address = "http://www.sina.com.cn/";
                HttpWorkerRequest wr 
    = new MyWorkerRequest("login.aspx""", tw, address);
                HttpContext.Current 
    = new HttpContext(wr);

    其实说这个事情,本身并不是为了这个技巧,而是想借这个例子说明怎么去考虑层的职责。比如对HttpContext这个东西,因为你知道你现在设计的是Web程序,你直接使用了这个。但如果有一天同样的业务,让你做一个WinForm呢?HttpContext该怎么办?所以,这样一分析就知道HttpContext这个东西肯定不属于逻辑层。

    而如果你对逻辑层做单元测试的话,那么你必定会遇到上述问题。而一旦遇到这种问题,应该就说明了你的设计思路有问题,因为从逻辑本身来说,实现一个测试,我不应该需要借助任何的模拟。至于Mock这个东西,留给以后的篇幅吧。

  • 相关阅读:
    UVA 10056 What is the Probability ?
    Reporting Services Report 带参数
    頁面刷新後,滾動條位置保持不變
    use this as the default and do not ask again
    JQuery Tab 滑动们导航菜单效果
    poj3256
    poj2060
    poj3280
    poj3261
    poj2135
  • 原文地址:https://www.cnblogs.com/gamix/p/about_httpcontext_in_unittest.html
Copyright © 2020-2023  润新知