如何在ASP.NET WebForms项目中使用单元测试

更新时间:2019-09-27 来源:ASP论文 点击:

【www.rjdtv.com--ASP论文】

摘要

  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 项目中使用单元测试。

  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.NET WebForms 框架的默认开发方式鼓励在后台代码文件中编写页面逻辑代码,但这并不符合单元测试的要求。 我们应该用一种页面和后台代码尽可能清晰简洁的方式来开发 ASP.NET WebForms 项目, 最可行的方法就是在项目开发时遵循 MVP(Model-View-Presenter)设计模式[4-5]。

  当遵循 MVP 模式开发 ASP.NET WebForms 项目时,与常规开发方式最大的不同就是将后台代码中所有的页面逻辑转移到一个单独的类之中,然后通过一个定义了页面行为的特定接口让页面逻辑类与页面联系起来。 遵循 MVP 模式开发的 ASP.NET WebForms 项目让我们可以清晰的分割 ASP.NET WebForms 框架代码和页面逻辑代码, 只有这样我们才能为项目添加可行的单元测试代码。

  2 ASP.NET WebForms 项目开发模式设计

  假设现在有一个 ASP.NET WebForms 页面需要开发并编写单元测试代码,这个页面中包含两个控件,按钮(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>{

  "张三","李四","王五","赵六"

  };

  View.Bind();

  }

  }

 

  MainController 类通过 GetData 方法实现了获取服务器数据的功能(本文不涉及数据处理,所以数据直接给出),并通过 DataRequest 事件将此功能与页面挂钩。 控制器类的所有代码都是独立于 ASP.NET 生命周期存在的,所以能够很方便的 对 其 编 写 单 元 测 试 来 验 证 功 能 是 否 正 确 。 由 于MainController 类包含了几乎全部的页面页面逻辑代码 ,如果能够通过单元测试确保 MainController 类的正确性,也就能保证整个页面功能的正确性。

  3 实现 ASP.NET WebForms 项目单元测试

  实现对 ASP.NET WebForms 项目的单元测试, 主要就是实现对 WebForms 页面的单元测试。 遵循上一节所提到的MVP 开发模式编写 ASP.NET WebForms 项目页面 ,可以让全部的页面逻辑实现代码集中到一个独立于 ASP.NET 生命周期存在的类当中,也就是说,如果我们能够编写单元测试代码检测页面逻辑类与页面是否正确连接和检测页面逻辑相关代码是否正确执行的话,也就相当于完成了对整个页面的单元测试。

  在上一节提到的例子中,MainController 是独立于 ASP.NET 生命周期存在并包含全部页面页面逻辑代码的类。 接下来, 我们可以编写单元测试验证该类是否与页面正确连接,也就是要验证 DataRequest 事件是否正确关联了一个事件处理程序。 在测试这一部分时可以忽略这个事件处理程序的具体内容。 测试代码如下:
 

  [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 项目页面代码的单元测试。

  4 结束语

  虽然遵循 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.NETWebForms 框架好很多。

  参考文献:

  [1] 张旭,王鹏,习媛媛,等. 单元测试在软件质量保证中的应用研究[J]. 煤炭技术,2010,29(6):185-186.ZHANG Xu,WANG Peng,XI Yuan-yuan,et al. Applicationof unit testing to software quality 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 modelin a simple way [J]. Computer Knowledge and Technology,2007,2(11):1314-1315.

本文来源:http://www.rjdtv.com/jisuanjilunwen/1409.html