董寧
(武漢軟件工程職業(yè)學(xué)院 湖北 武漢 430205)
ASP.NET WebForms是2002年作為.NET平臺的一部分發(fā)布的,它的發(fā)布對全世界的開發(fā)人員,是一個重要的里程碑。ASP.NET WebForms這一開發(fā)框架能夠讓我們使用C#語言和Visual Studio.NET環(huán)境開發(fā)強大的Web應(yīng)用程序。ASP.NET WebForms框架的優(yōu)勢是可以讓我們利用內(nèi)置控件快速開發(fā)Web應(yīng)用程序,它所提供的控件可以實現(xiàn)數(shù)據(jù)庫數(shù)據(jù)的編輯、刪除與格式化顯示和成員管理等幾乎全部的常用Web應(yīng)用程序功能。
在使用ASP.NET WebForms框架開發(fā)Web應(yīng)用程序時,開發(fā)人員主要的工作就是將控件拖放到頁面上,然后發(fā)布網(wǎng)站。這種開發(fā)模式可以快速的生成Web應(yīng)用程序,但并沒有考慮應(yīng)用程序的體系結(jié)構(gòu)和可測試性。開發(fā)人員很快發(fā)現(xiàn),隨著項目需求的不斷增長,用ASP.NET WebForms框架開發(fā)項目會面臨一系列嚴(yán)重問題。問題之一就是我們無法有效的對ASP.NET WebForms項目代碼進行單元測試[1]。
本文將重點探討如何在ASP.NET WebForms項目中使用單元測試。
一個典型ASP.NET WebForms應(yīng)用程序由兩個主要部分組成。一是后綴名為aspx的頁面文件,由ASP.NET標(biāo)記編寫而成,這種標(biāo)記混合了HTML和ASP.NET控件,并且包含了在服務(wù)器上執(zhí)行的C#程序代碼。第二部分是與aspx頁面文件對應(yīng)的后綴名為aspx.cs的后臺代碼文件,這些文件由C#代碼編寫而成,用以支持頁面文件并與頁面生命周期[2]掛鉤,這些代碼在頁面加載時和頁面響應(yīng)用戶請求時在服務(wù)器上執(zhí)行。
由于后綴名為aspx.cs的后臺代碼文件與頁面生命周期掛鉤,因此它們的執(zhí)行緊密依賴于ASP.NET核心運行庫。在進行測試時,這種依賴會導(dǎo)致大量問題,例如在編寫單元測試時,將嘗試在ASP.NET運行庫環(huán)境外執(zhí)行代碼,這樣會導(dǎo)致出現(xiàn)大量的ASP.NET運行庫相關(guān)錯誤消息。而對于后綴名為aspx的頁面文件,頁面文件中的任何代碼都是不能被其它類所訪問的,因此將無法為其編寫任何單元測試。同時,ASP.NET WebForms框架還無法讓頁面保持輕量級,因為頁面當(dāng)中往往會包含大量的控件[3],如下面給出的這個GridView控件示例:
<asp:GridView ID="grd1"runat="server"DataSourceID="obj1"
AutoGenerateColumns="false"
AllowPaging="true">
<Columns>
<asp:CommandField ShowSelectButton="true"/>
<asp:BoundField DataField="Name"HeaderText="
姓名"
SortExpression="Name"/>
</Columns>
</asp:GridView>
上述類型的代碼會讓頁面文件變得凌亂,同時由頁面文件在服務(wù)器端運行生成的HTML代碼也會過于復(fù)雜。如果按這種方式開發(fā)ASP.NET WebForms項目,則意味無法清晰的分割代碼,導(dǎo)致沒有可行的方式來編寫單元測試,甚至進行自動化的用戶界面測試都是很困難的。
為了能夠在ASP.NET WebForms項目中使用單元測試,我們應(yīng)該以一種不同的方式來開發(fā)應(yīng)用程序,要讓頁面文件和后臺代碼盡可能地“瘦”,也就是說它們應(yīng)當(dāng)盡量不包含實際頁面邏輯代碼,而是將頁面邏輯代碼放到一個單獨的類中。雖然ASP.NETWebForms框架的默認(rèn)開發(fā)方式鼓勵在后臺代碼文件中編寫頁面邏輯代碼,但這并不符合單元測試的要求。我們應(yīng)該用一種頁面和后臺代碼盡可能清晰簡潔的方式來開發(fā)ASP.NET WebForms項目,最可行的方法就是在項目開發(fā)時遵循 MVP(Model-View-Presenter)設(shè)計模式[4-5]。
當(dāng)遵循MVP模式開發(fā)ASP.NET WebForms項目時,與常規(guī)開發(fā)方式最大的不同就是將后臺代碼中所有的頁面邏輯轉(zhuǎn)移到一個單獨的類之中,然后通過一個定義了頁面行為的特定接口讓頁面邏輯類與頁面聯(lián)系起來。遵循MVP模式開發(fā)的ASP.NET WebForms項目讓我們可以清晰的分割A(yù)SP.NET WebForms框架代碼和頁面邏輯代碼,只有這樣我們才能為項目添加可行的單元測試代碼。
假設(shè)現(xiàn)在有一個ASP.NETWebForms頁面需要開發(fā)并編寫單元測試代碼,這個頁面中包含兩個控件,按鈕(Button)和列表框(ListBox),頁面要實現(xiàn)的具體功能是當(dāng)用戶單擊按鈕時向服務(wù)器請求數(shù)據(jù)并在列表框中顯示數(shù)據(jù)。
遵循MVP設(shè)計模式不難發(fā)現(xiàn),該頁面的具體功能可以通過一個接口抽象出來。該接口需要定義一個請求服務(wù)器數(shù)據(jù)的事件以便頁面調(diào)用,同時還需要一個存儲數(shù)據(jù)的屬性和一個讓頁面更新和顯示這一數(shù)據(jù)的方法。接口定義代碼如下:
public interface IMainView{
event EventHandler DataRequest;
List<string> Data{set; get; }
void Bind();
}
接下來,讓頁面對象實現(xiàn)該接口,這樣我們就可以在單獨的類里實現(xiàn)該接口,并將類實例傳遞給頁面,具體頁面代碼如下:
public partial class _Default:System.Web.UI.Page,IMainView{
public event EventHandler DataRequest;
public List<string> Data{get; set; }
private MainController Controller;
protected void Page_Load(object sender, EventArgs e){
Controller=new MainController(this);
}
public void Bind(){
ListBox1.DataSource=Data;
ListBox1.DataBind();
}
protected void Button1_Click(object sender, EventArgs e)
{
if(DataRequest!=null)
DataRequest(sender, e);
} }
在頁面代碼中,名為MainController的類充當(dāng)了MVP設(shè)計模式中控制器的角色,頁面類在初始化時,將自身的實例發(fā)送給了控制器,而控制器存儲這一頁面類實例用于之后的方法調(diào)用。
MainController類在初始化時必須與頁面所需的所有事件掛鉤,并將它們關(guān)聯(lián)到恰當(dāng)?shù)姆椒ㄉ?,以確保正確實現(xiàn)頁面功能。MainController類實現(xiàn)如下:
public class MainController{
public IMainView View{get; set; }
public MainController(IMainView view) {
View=view;
View.DataRequest+=GetData;
}
public void GetData(object sender,EventArgs e) {
View.Data=new List<string>{
對于商場來說,其競爭力的體現(xiàn)通常為兩個方面,即服務(wù)質(zhì)量和商品更新的速度。商場的經(jīng)濟效益受很多方面的影響,其中室內(nèi)環(huán)境是最為重要的影響因素。若環(huán)境舒適,能最大程度地激起顧客的消費欲望。商品銷售速度加快,競爭力得以提升。室內(nèi)設(shè)計是競爭力提升的基礎(chǔ)保障,商場效益提升是環(huán)境設(shè)計的直接體現(xiàn),兩者相輔相成,缺一不可。圖1是某商場的內(nèi)部結(jié)構(gòu)圖,僅供參考。
"張三","李四","王五","趙六"
};
View.Bind();
}
}
MainController類通過GetData方法實現(xiàn)了獲取服務(wù)器數(shù)據(jù)的功能(本文不涉及數(shù)據(jù)處理,所以數(shù)據(jù)直接給出),并通過DataRequest事件將此功能與頁面掛鉤??刂破黝惖乃写a都是獨立于ASP.NET生命周期存在的,所以能夠很方便的對其編寫單元測試來驗證功能是否正確。由于MainController類包含了幾乎全部的頁面頁面邏輯代碼,如果能夠通過單元測試確保MainController類的正確性,也就能保證整個頁面功能的正確性。
實現(xiàn)對ASP.NET WebForms項目的單元測試,主要就是實現(xiàn)對WebForms頁面的單元測試。遵循上一節(jié)所提到的MVP開發(fā)模式編寫ASP.NET WebForms項目頁面,可以讓全部的頁面邏輯實現(xiàn)代碼集中到一個獨立于ASP.NET生命周期存在的類當(dāng)中,也就是說,如果我們能夠編寫單元測試代碼檢測頁面邏輯類與頁面是否正確連接和檢測頁面邏輯相關(guān)代碼是否正確執(zhí)行的話,也就相當(dāng)于完成了對整個頁面的單元測試。
[TestClass]
public class MainControllerTests{
[TestMethod]
public void CtorIsHookupEvents(){
IMainView view = MockRepository.GenerateMock <IMainView>();
view.Expect (view => view.DataRequested += null).IgnoreArguments();
new MainController(view);
view.VerifyAllExpectations();
}
}
上述代碼使用了RhinoMocks測試框架,該框架可以根據(jù)IMainView接口模擬出視圖實例用于測試。這一部分重點測試了試圖實例被傳遞給MainController類后其中的DataRequested事件是否關(guān)聯(lián)了[3,4]程序,也就是被執(zhí)行了“+=”操作。
在對事件實現(xiàn)單元測試后,下一步就是測試在事件發(fā)生時被調(diào)用的GetData方法。GetData方法的作用是在被調(diào)用時填充視圖中的Data屬性。同樣的,在測試時還是利用RhinoMocks測試框架,根據(jù)IMainView接口模擬出視圖實例,然后根據(jù)該實例創(chuàng)建出頁面邏輯類實例,也就是MainController類實例,就好像是一個真正的頁面在運行一樣。接下來直接調(diào)用GetData方法,然后檢測Data屬性是否被正確填充。具體的單元測試代碼如下:
[TestMethod()]
public void GetDataIsPopulateData(){
IMainView view = MockRepository.GenerateStub <IMainView>();
MainController controller=new MainController(view);
controller.GetData(this, EventArgs.Empty);
Assert.AreEqual(4, view.Data.Count);
}
如果頁面邏輯類在調(diào)用GetData方法后正確填充了Data屬性,那么上述測試將正確通過,否則單元測試測試會失敗。
對于ASP.NET WebForms頁面來說,在獲取數(shù)據(jù)后需要通知視圖來刷新用戶界面并向用戶顯示數(shù)據(jù),所以最后要測試的部分就是確保頁面邏輯類能夠正確調(diào)用頁面視圖類中的Bind方法。在這里同樣可以使用RhinoMocks測試框架創(chuàng)建出一個模擬的MainController類實例,來驗證調(diào)用GetData方法后Bind方法也會被正確調(diào)用。具體代碼如下:
[TestMethod()]
public void GetDataIsCallBind(){
IMainView view = MockRepository.GenerateMock <IMainView>();
view.Expect(v=> v.Bind());
MainController controller=new MainController(view);
controller.GetData(this, EventArgs.Empty);
view.VerifyAllExpectations();
}
至此,可以說完全實現(xiàn)了對上節(jié)ASP.NET WebForms項目頁面代碼的單元測試。
雖然遵循MVP模式開發(fā)ASP.NET WebForms項目可以提高代碼的可測試性并實現(xiàn)WebForms頁面的單元測試,但這種方法并不完美。因為ASP.NET WebForms本身的架構(gòu)決定了不可能能將100%的項目代碼都納入到單元測試中來。比如上節(jié)例子中的兩個函數(shù)就無法被包含到單元測試中,其代碼如下:
public void Bind(){
ListBox1.DataSource=Data;
ListBox1.DataBind();
}
protected void Button1_Click(object sender, EventArgs e)
{
if(DataRequest!=null)
DataRequest(sender, e);
}
盡管上述兩個方法不經(jīng)測試也不會引起大問題,但不難想象,隨著項目的增長,這種未經(jīng)測試的代碼也會增加,從而增加錯誤代碼被引入到項目中的可能性。
所以,除非是對現(xiàn)有項目進行重構(gòu),一般不建議用MVP模式開發(fā)ASP.NET WebForms項目。其實MVP模式也并不是為ASP.NET WebForms項目開發(fā)的主流方法,它會增加項目的復(fù)雜性,而且微軟官方對該模式也沒有提供支持。如果需要利用ASP.NET開發(fā)可測試性高的Web項目,還是建議選用微軟新發(fā)布的的基于MVC模式的Web開發(fā)框架,稱為ASP.NET MVC[6],該框架在可測試性支持方面比ASP.NET WebForms框架好很多。
[1]張旭,王鵬,習(xí)媛媛,等.單元測試在軟件質(zhì)量保證中的應(yīng)用研究[J].煤炭技術(shù),2010,29(6):185-186.ZHANG Xu,WANG Peng,XI Yuan-yuan,et al.Application of unittestingtosoftwarequality assurance[J].Coal Technology,2010,29(6):185-186.
[2]江艷萍.深入淺出ASP.NET頁面對象模型 [J].電腦知識與技術(shù),2007, 2(11):1314-1315.JIANG Yan-ping.Understanding asp.net page object model in a simple way[J].Computer Knowledge and Technology,2007,2(11):1314-1315.
[3]馬潔,周靜.基于ASP.NET控件定義的分析與比較[J].通信技術(shù),2010,43(4):144-146.MA Jie,ZHOU Jing.Analysis and comparion of the control definition based on asp.net[J].Communications Technology,2010,43(4):144-146.
[4]顧明霞,蔡長安.WebForms、MVC和MVP在ASP.NET開發(fā)中的對比分析[J].重慶工商大學(xué)學(xué)報:自然科學(xué)版,2011,28(4):394-397,409.GUMing-xia,CAIChang-an.Comparativeanalysisof webforms,MVC and MVP architecture in asp.net development[J].Journal of Chongqing Technology and Business:Natural Sciences Edition,2011,28(4):394-397,409.
[5]劉海巖,鎖志海,呂青,等.設(shè)計模式及其在軟件設(shè)計中的應(yīng)用研究[J].西安交通大學(xué)學(xué)報,2005,39(10):1043-1047.LIU Hai-yan,SUO Zhi-hai,LV Qing,et al.Design patterns and their applications to software design[J].Journal of Xi'an Jiaotong University,2005, 39(10):1043-1047.
[6]陳曉丹,鄭毅.ASP.NET開發(fā)環(huán)境下的WebForm與MVC設(shè)計模式[J].武漢工程職業(yè)技術(shù)學(xué)院學(xué)報,2009,21(2):38-40,34.CHEN Xiao-dan,ZHENG Yi.Comparison of ASP webform and MVC[J].Journal of Wuhan Engineering Institute,2009,21(2):38-40,34.