惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

H
Help Net Security
博客园 - Franky
GbyAI
GbyAI
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
爱范儿
爱范儿
IT之家
IT之家
酷 壳 – CoolShell
酷 壳 – CoolShell
aimingoo的专栏
aimingoo的专栏
博客园_首页
MongoDB | Blog
MongoDB | Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Recent Announcements
Recent Announcements
Scott Helme
Scott Helme
有赞技术团队
有赞技术团队
M
MIT News - Artificial intelligence
C
CERT Recently Published Vulnerability Notes
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Jina AI
Jina AI
F
Fortinet All Blogs
N
Netflix TechBlog - Medium
L
LangChain Blog
L
LINUX DO - 最新话题
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
H
Hacker News: Front Page
MyScale Blog
MyScale Blog
P
Palo Alto Networks Blog
G
Google Developers Blog
Google DeepMind News
Google DeepMind News
AI
AI
T
Troy Hunt's Blog
Microsoft Azure Blog
Microsoft Azure Blog
阮一峰的网络日志
阮一峰的网络日志
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Vercel News
Vercel News
Microsoft Security Blog
Microsoft Security Blog
罗磊的独立博客
S
Secure Thoughts
大猫的无限游戏
大猫的无限游戏
博客园 - 叶小钗
人人都是产品经理
人人都是产品经理
Blog — PlanetScale
Blog — PlanetScale
博客园 - 司徒正美
Apple Machine Learning Research
Apple Machine Learning Research
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 三生石上(FineUI控件)
S
Security @ Cisco Blogs
Cloudbric
Cloudbric
E
Exploit-DB.com RSS Feed
Attack and Defense Labs
Attack and Defense Labs

博客园 - 枫

2015半年记 再写一帖~就《离开上海》一文再说明 再见,上海~非主流码农在上海的9年心路历程 Mac下配置mysql-python 《Rework》摘录读后随感 Event Sourcing与大数据 无题(1) 在Mac上安装Hadoop [原创]一步一步用C#编写三国杀(三):设计流程 [原创]一步一步用C#编写三国杀(二):牌堆的设计 [原创]一步一步用C#编写三国杀(一):规则和需求描述 走进单元测试(2):必须要自动化 我也想对广大程序员说一些话 走进单元测试(1):为什么难以广泛应用? 高级语言发展之回归人类思维——听老赵的Session有感 缺乏自信怎么办? 梦话对象之三:三要素的差异与统一 梦话对象之二:事件之无限扩展 梦话对象之一:逃不开的生死问题
走进单元测试(3):消灭HttpContext的依赖,兼谈单元测试的设计辅助性
· 2010-06-03 · via 博客园 - 枫

前篇提到过由于我们已经有了一个现成的平台,现在要对其进行单元测试的补完。而在这个过程中,就出现了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这个东西,留给以后的篇幅吧。