雖然身為開發老兵,最近才開始認真練習單元測試,不小心學到新技巧還會高興半天,嗯,那是一種,說不出來,但,確實,真的,又存在的感覺,是彷彿在魁北克無邊無際的,沙漠,之中的冰原般,的寂寞向日葵,專屬的小確幸(抱歉! 誤啟假文青模式)。今天就來分享一則幼幼班經驗 -- 當程式用到HttpRequest,要如何寫單元測試?
假設我們有個RequestParser類別,提供Parse(HttpRequestBase request)方法,由HttpRequestBase中取出指定Cookie、客戶端IP及UserAgent。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
namespace WebHelper
{
publicclass ClientInfo
{
publicstring SrcCookie;
publicstring ClientIp;
publicstring UserAgent;
}
publicclass RequestParser
{
publicstatic ClientInfo Parse(HttpRequestBase request)
{
returnnew ClientInfo()
{
SrcCookie = (request.Cookies["src"] ??
new HttpCookie("src", string.Empty)).Value,
ClientIp = request.UserHostAddress,
UserAgent = request.UserAgent
};
}
}
}
講到這裡,應該已經有WebForm專修班同學舉手發問: "為什麼是用HttpRequestBase,不是HttpRequest嗎?"
故事是這樣的: HttpRequest誕生於.NET 1.1時代,屬於System.Web命名空間,可透過Page.Request及UserControl.Request存取,具唯讀性質。意思是你只能從Page或UserControl取得HttpRequest物件,無法自己建立或修改,這大大限制了它的應用彈性,難以進行單元測試,除非丟到網站,你很難生出一個HttpRequest物件執行測試案例。
有鑑於此,.NET 3.5 SP1加入了HttpRequestBase,具備HttpRequest所有屬性,但差別在於它是抽象類別,開發者可以繼承它寫出衍生類別加入客製邏輯。原本依賴HttpRequest的程式只需改用HttpRequestBase,程式碼毋須修改(因HttpRequstBase的介面與HttpRequest完全相同),這允許我們自行建立及操控HttpRequestBase參數進行測試及應用。而在IIS環境執行時,可使用HttpRequestWrapper將HttpRequest轉為HttpRequestBase,傳給改用HttpRequestBase型別的方法。像在ASP.NET MVC,Controller裡已不見HttpRequest蹤跡,全面改用HttpRequestBase。
寫個WebForm測試RequestParser:
using System;
using System.Web;
namespace Web
{
publicpartialclass Default : System.Web.UI.Page
{
protectedvoid Page_Load(object sender, EventArgs e)
{
var client = WebHelper.RequestParser.Parse(
new HttpRequestWrapper(this.Request));
Response.Write("<li>Client IP=" + client.ClientIp);
Response.Write("<li>Cookie=" + client.SrcCookie);
Response.Write("<li>UserAgent=" + client.UserAgent);
Response.End();
}
}
}
執行無誤!
把場景切到單元測試,在純Class Library中,要怎麼生出一個HttpRequestBase呢?
繼承HttpRequestBase寫一個MyHttpRequest是種做法,但我們的測試只用到Cookies、UserHostAddress、UserAgent三個屬性,為此寫一顆HttpRequestBase衍生類別實作數十個屬性方法形同"為了拔一根牛毛養一頭牛",人生苦短,嗯湯啊嗯湯。在測試世界裡,通常會用Mock、Stub及Fake技巧解決問題。
爬文得知目前較多人用的.NET Mock元件有Rhino Mocks及Moq,該用哪一個呢? 就使用"拿香跟著拜理論"決定吧!
RhinoMocks 最後更新於 2012/3/18,下載次數26萬次
Moq 最後更新於 2014/2/21,下載次數145萬次
二者相比,猶如德國 7: 1 踢垮巴西,用膝蓋也知道該怎麼選。更重要的一點是,Moq API大量使用Lambda,令LINQ成癮的我心醉,就用Moq吧!
Moq的使用方法很簡單,使用new Mock<HttpRequestBase>()可建立HttpRequestBase的Mock物件,接著以三個SetupGet()設定UserHostAddress、Cookies及UserAgent屬性的讀取行為。SetupGet()使用x => x.SomeProperty的Lambda語法指定要模擬的屬性。而如下圖所示,Moq巧妙地透過泛型宣告讓輸入Lambda時也有Intellisense支援,想打錯字都難(按個讚!)。每個SetupGet()後接上Returns()決定傳回結果。就這樣,我們模擬出一個只有HostUserAddress、Cookies、UserAgent三個屬性有效的陽春HttpRequestBase物件。
測試時,Mock<HttpRequestBase>.Object可當成HttpRequestBase使用,執行結果也如同預期。
藉由Mock物件,我們可以自由捏塑出所需參數型別並指定其行為,不需太多周邊環境配合,就能完成單元測試囉~ 關於Moq的更多說明,可參考官方文件。