概述
在閱讀本文之前,兄弟們請先注意兩點:
我們現在談的是傳統ASP.NET應用程序的可測試性,而不是ASP.NET MVC應用程序的可測試 性。
我們現在談的是“增強”,而不是說傳統ASP.NET應用程序做不到良好的可測試性,一切 皆在人為。
關於可測試性的重要性,老趙覺得已經不需要再過多強調了。如果您想要獲得高生產力, 為代碼編寫單元測試似乎已經是必經之路了。不過可惜的是,ASP.NET應用程序給人的感覺, 始終是對可測試性不太友好,其最重要的原因之一在於對HttpContext對象的高度依賴,而我 們很難對HttpContext編寫Mock或Stub:對於最常見的Mock框架來說,進行Mock的方式在於對 抽象類型進行繼承和重寫,因此需要目標類型必須能夠繼承,其成員也必須能夠重寫 (override),可惜HttpContext對這兩個要求均不滿足——雖然我們有TypeMock這個強大的 工具,只可惜它是商業產品。而且事實上,如果Moq等框架無法滿足您的要求,一般可以確定 是設計有問題。從這個角度說,ASP.NET圍繞HttpContext開展的一系列功能,在設計上的確 有不足之處。
因此,為了提高ASP.NET應用程序的可測試性,各方都作了許多努力,其中的原則便是: 盡可能減少對HttpContext的依賴(不可測試的邏輯),使邏輯依賴於特定的抽象類型。“特 定”二字是指與您的業務或功能相關性,例如您在使用MVP模式進行開發時,使用的每個類型 都是領域相關(如User),或界面相關(如SelectList)的抽象類型,而不是具體的界面( 如DropDownList)或協議(HttpContext1)相關類型。這往往需要您在具體類型 上多加一個抽象層,針對抽象進行編程。除了MVP模式之外,ASP.NET AJAX中的 PageRequestManager也是如此,ScriptManager的各階段操作都簡單地委托給了 PageRequestManager,這樣不可測試的邏輯(ScriptManager)減少了,可以測試的邏輯 (PageRequestManager)增加了。
不過可以想到的是,圍繞HttpContext進行編程的場景也是不可避免的,例如Http Handler/Module等ASP.NET基礎結構,亦或是連接HttpContext與抽象類型的“黏著劑”。關 於這方面微軟也在改進,例如隨ASP.NET MVC發布了ASP.NET Abstraction,其中提供了抽象 類型HttpContextBase(老趙個人不喜歡Base這樣的後綴,其實更喜歡IHttpContext這樣的接 口類型),這是一個赤裸裸地抽象類,其中包含了HttpContext的所有成員,個個抽象。也正 是由於這樣的抽象,使得圍繞HttpContext進行單元測試的可行性大大增加了。當然,這句話 有個前提,那就是以前圍繞HttpContext編寫的代碼,現在要使用HttpContextBase了,這也 是提高ASP.NET應用程序可測試性的又一原則:對於一定要依賴HttpContext的邏輯,請依賴 HttpContextBase。那麼現在,兄弟們就隨老趙來看一下,如何使用ASP.NET Abstraction來 輔助ASP.NET開發。
直接使用HttpContext進行測試
HttpContext對象難以Mock,但是也並非說它的數據我們就無法“定制”,在某些“極端 簡單”的情況下,我們還是可以直接構造一個HttpContext對象進行測試的。比如下面這個毫 無意義的Http Handler:
public class CountDataHandler : IHttpHandler
{
public bool IsReusable { get { return true; } }
public void ProcessRequest(HttpContext context)
{
string data = context.Request.QueryString["data"];
if (data == null)
{
throw new ArgumentNullException("data");
}
context.Response.Write(data.Length);
}
}