董宁
(武汉软件工程职业学院 湖北 武汉 430205)
ASP.NET WebForms是2002年作为.NET平台的一部分发布的,它的发布对全世界的开发人员,是一个重要的里程碑。ASP.NET WebForms这一开发框架能够让我们使用C#语言和Visual Studio.NET环境开发强大的Web应用程序。ASP.NET WebForms框架的优势是可以让我们利用内置控件快速开发Web应用程序,它所提供的控件可以实现数据库数据的编辑、删除与格式化显示和成员管理等几乎全部的常用Web应用程序功能。
在使用ASP.NET WebForms框架开发Web应用程序时,开发人员主要的工作就是将控件拖放到页面上,然后发布网站。这种开发模式可以快速的生成Web应用程序,但并没有考虑应用程序的体系结构和可测试性。开发人员很快发现,随着项目需求的不断增长,用ASP.NET WebForms框架开发项目会面临一系列严重问题。问题之一就是我们无法有效的对ASP.NET WebForms项目代码进行单元测试[1]。
本文将重点探讨如何在ASP.NET WebForms项目中使用单元测试。
一个典型ASP.NET WebForms应用程序由两个主要部分组成。一是后缀名为aspx的页面文件,由ASP.NET标记编写而成,这种标记混合了HTML和ASP.NET控件,并且包含了在服务器上执行的C#程序代码。第二部分是与aspx页面文件对应的后缀名为aspx.cs的后台代码文件,这些文件由C#代码编写而成,用以支持页面文件并与页面生命周期[2]挂钩,这些代码在页面加载时和页面响应用户请求时在服务器上执行。
由于后缀名为aspx.cs的后台代码文件与页面生命周期挂钩,因此它们的执行紧密依赖于ASP.NET核心运行库。在进行测试时,这种依赖会导致大量问题,例如在编写单元测试时,将尝试在ASP.NET运行库环境外执行代码,这样会导致出现大量的ASP.NET运行库相关错误消息。而对于后缀名为aspx的页面文件,页面文件中的任何代码都是不能被其它类所访问的,因此将无法为其编写任何单元测试。同时,ASP.NET WebForms框架还无法让页面保持轻量级,因为页面当中往往会包含大量的控件[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>
上述类型的代码会让页面文件变得凌乱,同时由页面文件在服务器端运行生成的HTML代码也会过于复杂。如果按这种方式开发ASP.NET WebForms项目,则意味无法清晰的分割代码,导致没有可行的方式来编写单元测试,甚至进行自动化的用户界面测试都是很困难的。
为了能够在ASP.NET WebForms项目中使用单元测试,我们应该以一种不同的方式来开发应用程序,要让页面文件和后台代码尽可能地“瘦”,也就是说它们应当尽量不包含实际页面逻辑代码,而是将页面逻辑代码放到一个单独的类中。虽然ASP.NETWebForms框架的默认开发方式鼓励在后台代码文件中编写页面逻辑代码,但这并不符合单元测试的要求。我们应该用一种页面和后台代码尽可能清晰简洁的方式来开发ASP.NET WebForms项目,最可行的方法就是在项目开发时遵循 MVP(Model-View-Presenter)设计模式[4-5]。
当遵循MVP模式开发ASP.NET WebForms项目时,与常规开发方式最大的不同就是将后台代码中所有的页面逻辑转移到一个单独的类之中,然后通过一个定义了页面行为的特定接口让页面逻辑类与页面联系起来。遵循MVP模式开发的ASP.NET WebForms项目让我们可以清晰的分割ASP.NET WebForms框架代码和页面逻辑代码,只有这样我们才能为项目添加可行的单元测试代码。
假设现在有一个ASP.NETWebForms页面需要开发并编写单元测试代码,这个页面中包含两个控件,按钮(Button)和列表框(ListBox),页面要实现的具体功能是当用户单击按钮时向服务器请求数据并在列表框中显示数据。
遵循MVP设计模式不难发现,该页面的具体功能可以通过一个接口抽象出来。该接口需要定义一个请求服务器数据的事件以便页面调用,同时还需要一个存储数据的属性和一个让页面更新和显示这一数据的方法。接口定义代码如下:
public interface IMainView{
event EventHandler DataRequest;
List<string> Data{set; get; }
void Bind();
}
接下来,让页面对象实现该接口,这样我们就可以在单独的类里实现该接口,并将类实例传递给页面,具体页面代码如下:
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的类充当了MVP设计模式中控制器的角色,页面类在初始化时,将自身的实例发送给了控制器,而控制器存储这一页面类实例用于之后的方法调用。
MainController类在初始化时必须与页面所需的所有事件挂钩,并将它们关联到恰当的方法上,以确保正确实现页面功能。MainController类实现如下:
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>{
对于商场来说,其竞争力的体现通常为两个方面,即服务质量和商品更新的速度。商场的经济效益受很多方面的影响,其中室内环境是最为重要的影响因素。若环境舒适,能最大程度地激起顾客的消费欲望。商品销售速度加快,竞争力得以提升。室内设计是竞争力提升的基础保障,商场效益提升是环境设计的直接体现,两者相辅相成,缺一不可。图1是某商场的内部结构图,仅供参考。
"张三","李四","王五","赵六"
};
View.Bind();
}
}
MainController类通过GetData方法实现了获取服务器数据的功能(本文不涉及数据处理,所以数据直接给出),并通过DataRequest事件将此功能与页面挂钩。控制器类的所有代码都是独立于ASP.NET生命周期存在的,所以能够很方便的对其编写单元测试来验证功能是否正确。由于MainController类包含了几乎全部的页面页面逻辑代码,如果能够通过单元测试确保MainController类的正确性,也就能保证整个页面功能的正确性。
实现对ASP.NET WebForms项目的单元测试,主要就是实现对WebForms页面的单元测试。遵循上一节所提到的MVP开发模式编写ASP.NET WebForms项目页面,可以让全部的页面逻辑实现代码集中到一个独立于ASP.NET生命周期存在的类当中,也就是说,如果我们能够编写单元测试代码检测页面逻辑类与页面是否正确连接和检测页面逻辑相关代码是否正确执行的话,也就相当于完成了对整个页面的单元测试。
[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测试框架,该框架可以根据IMainView接口模拟出视图实例用于测试。这一部分重点测试了试图实例被传递给MainController类后其中的DataRequested事件是否关联了[3,4]程序,也就是被执行了“+=”操作。
在对事件实现单元测试后,下一步就是测试在事件发生时被调用的GetData方法。GetData方法的作用是在被调用时填充视图中的Data属性。同样的,在测试时还是利用RhinoMocks测试框架,根据IMainView接口模拟出视图实例,然后根据该实例创建出页面逻辑类实例,也就是MainController类实例,就好像是一个真正的页面在运行一样。接下来直接调用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);
}
如果页面逻辑类在调用GetData方法后正确填充了Data属性,那么上述测试将正确通过,否则单元测试测试会失败。
对于ASP.NET WebForms页面来说,在获取数据后需要通知视图来刷新用户界面并向用户显示数据,所以最后要测试的部分就是确保页面逻辑类能够正确调用页面视图类中的Bind方法。在这里同样可以使用RhinoMocks测试框架创建出一个模拟的MainController类实例,来验证调用GetData方法后Bind方法也会被正确调用。具体代码如下:
[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();
}
至此,可以说完全实现了对上节ASP.NET WebForms项目页面代码的单元测试。
虽然遵循MVP模式开发ASP.NET WebForms项目可以提高代码的可测试性并实现WebForms页面的单元测试,但这种方法并不完美。因为ASP.NET WebForms本身的架构决定了不可能能将100%的项目代码都纳入到单元测试中来。比如上节例子中的两个函数就无法被包含到单元测试中,其代码如下:
public void Bind(){
ListBox1.DataSource=Data;
ListBox1.DataBind();
}
protected void Button1_Click(object sender, EventArgs e)
{
if(DataRequest!=null)
DataRequest(sender, e);
}
尽管上述两个方法不经测试也不会引起大问题,但不难想象,随着项目的增长,这种未经测试的代码也会增加,从而增加错误代码被引入到项目中的可能性。
所以,除非是对现有项目进行重构,一般不建议用MVP模式开发ASP.NET WebForms项目。其实MVP模式也并不是为ASP.NET WebForms项目开发的主流方法,它会增加项目的复杂性,而且微软官方对该模式也没有提供支持。如果需要利用ASP.NET开发可测试性高的Web项目,还是建议选用微软新发布的的基于MVC模式的Web开发框架,称为ASP.NET MVC[6],该框架在可测试性支持方面比ASP.NET WebForms框架好很多。
[1]张旭,王鹏,习媛媛,等.单元测试在软件质量保证中的应用研究[J].煤炭技术,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].电脑知识与技术,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].通信技术,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开发中的对比分析[J].重庆工商大学学报:自然科学版,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]刘海岩,锁志海,吕青,等.设计模式及其在软件设计中的应用研究[J].西安交通大学学报,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开发环境下的WebForm与MVC设计模式[J].武汉工程职业技术学院学报,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.