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 控件示例:
obj1"
AutoGenerateColumns="false"
AllowPaging="true">
姓名"
SortExpression="Name" />
上述类型的代码会让页面文件变得凌乱,同时由页面文件在服务器端运行生成的 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),页面要实现的具体功能是当用户单击按钮时向服务器请求数据并在列表框中显示数据。
实现对 ASP.NET WebForms 项目的单元测试, 主要就是实现对 WebForms 页面的单元测试。 遵循上一节所提到的MVP 开发模式编写 ASP.NET WebForms 项目页面 ,可以让全部的页面逻辑实现代码集中到一个独立于 ASP.NET 生命周期存在的类当中,也就是说,如果我们能够编写单元测试代码检测页面逻辑类与页面是否正确连接和检测页面逻辑相关代码是否正确执行的话,也就相当于完成了对整个页面的单元测试。
在上一节提到的例子中,MainController 是独立于 ASP.NET 生命周期存在并包含全部页面页面逻辑代码的类。 接下来, 我们可以编写单元测试验证该类是否与页面正确连接,也就是要验证 DataRequest 事件是否正确关联了一个事件处理程序。 在测试这一部分时可以忽略这个事件处理程序的具体内容。 测试代码如下: