测试运行

使用 JavaScript 实现 Web 应用程序请求-响应测试

James McCaffrey McCaffrey

下载代码示例

在本月专栏中,我将介绍如何使用 JavaScript 编写简单、有效的基于浏览器的请求-响应测试自动化程序。要想了解我所讲述的内容,最好是看一下图 1 和 2 所示的屏幕快照。图 1 是要进行“产品搜索”测试的 ASP.NET Web 应用程序,它简单但具有代表性。用户在此应用程序的单个文本框控件中输入一些搜索字符串,并使用两个单选按钮控件指定执行搜索时是否要区分大小写。搜索结果将在列表框控件中显示。

图 1 要测试的“产品搜索”Web 应用程序

image: Product Search Web Application Under Test

虽然要测试的示例 Web 应用程序基于 ASP.NET,但我在本文中介绍的技术也可用于为 Web 应用程序创建测试自动化程序,而这些应用程序是使用最具动态性的页面生成技术(包括 PHP、JSP、CGI 以及其他)编写而成。

图 2 显示的是正在运行的请求-响应测试自动化程序。请注意,测试自动化程序工具是基于浏览器的。与其他可选方法比较,此处介绍的技术的优势之一就是,它可与最主要的 Web 浏览器一起使用,并且可在运行大多数操作系统的测试主机上执行。

图 2 请求-响应测试运行

image: Request-Response Test Run

该测试自动化程序工具由单个 HTML 页面构成,该页面具有相对简短的一组 JavaScript 函数。请注意,测试运行输出的第一行指示测试自动化程序正在使用 jQuery 库。此工具将读取对应于用户输入的测试用例输入数据,然后以编程方式向“产品搜索”Web 应用程序提供该输入数据。该工具接受生成的 HTTP 响应数据并检查该响应是否为预期值,从而确定测试用例结果为通过还是失败。

在本文接下来的部分中,我将首先简要介绍图 1 中所示的要测试的 Web 应用程序,从而方便您了解哪些因素与 HTTP 请求-响应测试有关。然后,将详细解释在图 2 中运行的测试工具代码,这样您就能够为满足自身需求对该工具进行修改。最后,将简要总结何时适合使用 JavaScript 实现基于浏览器的请求-响应测试自动化程序,以及何时更加适合使用其他可选方法。

在本文中,假设您的 JavaScript 和 ASP.NET 技能为中级水平,但是即使您初学这些技术,也应该能理解我的讲解。

构建 Web 应用程序

我使用 Visual Studio 2008 创建要测试的“产品搜索”Web 应用程序。为了利用 Visual Studio 配置网站的功能,我在主菜单栏上选择了“文件”|“新建”|“网站”。然后,在生成的“新建网站”对话框中选择“空网站”选项。为创建完整的 ASP.NET 网站,我在本地计算机上指定了 HTTP 位置,而不是为使用内置 Visual Studio 开发服务器指定文件系统位置。我选择 C# 语言作为逻辑代码。

单击“确定”后,Visual Studio 就创建了空的“产品搜索”网站。在“解决方案资源管理器”窗口中,右键单击“产品搜索”项目,然后从上下文菜单中选择“添加新项”。我选择了“Web 窗体”项,接受了默认页面名称 Default.aspx 并单击“添加”,页面随即生成。然后,我为要测试的 Web 应用程序创建了简单的 UI,如图 3 中所示。

图 3 Web 应用程序 UI

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>Product Search</title>
</head>
<body bgcolor="#ccbbcc">
  <form id="form1" runat="server">
  <div>
    <asp:Label ID="Label1" runat="server" Text="Find:" 
      Font-Names="Arial" Font-Size="Small">
    </asp:Label>  
    <asp:TextBox ID="TextBox1" runat="server" Width="114px">
    </asp:TextBox>  
    <asp:Button ID="Button1" runat="server" onclick="Button1_Click" 
      Text="Go" />
    <br />

    <asp:RadioButtonList ID="RadioButtonList1" runat="server" 
      Font-Names="Arial" Font-Size="Small">
    <asp:ListItem>Case Sensitive</asp:ListItem>
    <asp:ListItem Selected="True">Not Case Sensitive</asp:ListItem>
    </asp:RadioButtonList>
  </div>
  <asp:ListBox ID="ListBox1" runat="server" Height="131px" Width="246px"
    Font-Names="Courier New" Font-Size="Small">
  </asp:ListBox>
  </form>
</body>
</html>

因为我将进行简要解释,所以,当创建 HTTP 请求-响应测试自动化程序时,您必须了解您希望模拟用户输入的任何输入控件的 ID。这样,我就能够访问要测试的应用程序的源代码,但是即使您没有源代码访问权限,也始终能够使用 Web 浏览器的查看源代码功能确定输入控件 ID。请注意,您或许认为这两个单选按钮控件由两个控件表示,但实际上是由一个 ID 为 RadioButtonList1 的输入控件表示。

我将此应用程序逻辑直接添加到 Defaut.aspx 文件,而没有使用代码隐藏机制。在页面的顶部,我创建了一个脚本块,用于放置应用程序的逻辑代码:

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
  // ...
</script>

我在该脚本块中添加了一个小类,用于代表 Product 对象:

public class Product {
  public int id;
  public string desc;
  public Product(int id, string desc) {
    this.id = id; this.desc = desc;
  }
}

然后又添加了一个内部应用程序作用域 ArrayList 对象,用于模拟外部数据存储:

public static ArrayList data = null;

在大多数实际的 Web 应用程序方案中,数据存储通常是外部的,如 XML 文件或 SQL Server 数据库。但是,当执行 HTTP 请求-响应测试时,在某种程度上,应用程序的数据存储位置无关紧要。HTTP 请求并不知道数据存储的位置,并且 HTTP 响应通常只包含 HTML。然后,我又添加了一些代码,用来使用 Product 项填充内部数据存储: 

protected void Page_Load(object sender, EventArgs e) {
  if (!IsPostBack) {
    data = new ArrayList();
    Product p1 = new Product(111, "Widget");
    Product p2 = new Product(222, "Gizzmo");
    Product p3 = new Product(333, "Thingy");
    data.Add(p1); data.Add(p2); data.Add(p3);
  }
}

最后,我将所有应用程序逻辑放置到事件处理程序中,以在 Button1 单击事件时调用。首先,清空 ListBox1 结果区域并提取用户输入:

ListBox1.Items.Clear();
string filter = TextBox1.Text.Trim();
string sensitivity = RadioButtonList1.SelectedValue;

区分大小写字符串变量包含“区分大小写”或“不区分大小写”。

之后,我将标头信息放置到 ListBox1 结果区域、声明一个保存 Product 搜索结果的字符串并初始化一个计数器以跟踪与搜索筛选器匹配的 Product 项的个数:

ListBox1.Items.Add("ID   Description");
ListBox1.Items.Add("================");
string resultRow;
int count = 0;

我在 ArrayList 数据存储检查中遍历每个 Product 对象,以查看搜索筛选器字符串是否与当前对象的说明字段匹配:

foreach (Product p in data) {
  resultRow = "";
  if (sensitivity == "Not Case Sensitive" &&
    p.desc.IndexOf(filter, 
    StringComparison.CurrentCultureIgnoreCase) >= 0) {
    resultRow = p.id + " " + p.desc; ++count;
  }
  else if (sensitivity == "Case Sensitive" && 
    p.desc.IndexOf(filter) >= 0) {
    resultRow = p.id + " " + p.desc; ++count;
  }
  if (resultRow != "") ListBox1.Items.Add(resultRow);
}

对于与搜索筛选器匹配的每个产品,我将构建一个结果字符串并增加计数器的值。请注意,IndexOf 方法适时进行重载以接受区分大小写参数。

应用程序逻辑最后会向 ListBox1 显示区域添加空白行和计数结果:

ListBox1.Items.Add("");
ListBox1.Items.Add("Found " + count + " matching items");

为使 Web 应用程序尽可能小并且使用简单,我使用了许多快捷方式,而这些快捷方式不会在生产环境中使用。尤其是,我并未提供任何错误检查或处理功能。

请求-响应测试自动化程序

我已使用记事本创建了测试工具页,如图 2 中运行的内容所示。此工具的整体结构如图 4 中所示。

图 4 测试工具结构

<html>
<!-- RequestResponseTests.html -->
<head>
  <script src=’http://localhost/TestWithJQuery/jquery-1.3.2.js’>
  </script>
  <script type="text/javascript">
    $(document).ready(function() {
      logRemark("jQuery Library found and harness DOM is ready\n");
    } );
  
    var targetURL = ‘http://localhost/TestWithJQuery/ProductSearch/Default.aspx’;

    var testCaseData =
[ ‘001,TextBox1=T&RadioButtonList1=Case+Sensitive&Button1=clicked,333 Thingy’,
‘002,TextBox1=t&RadioButtonList1=Not+Case+Sensitive&Button1=clicked,Found 2 matching items’ ];
        
    function runTests() {
      try {
        logRemark(‘Begin Request-Response with JavaScript test run’);
        logRemark("Testing Product Search ASP.NET application\n");
        // ...
        logRemark("\nEnd test run");
      }
      catch(ex) {
        logRemark("Fatal error: " + ex);
      }
    }
    
    function getVS(target) {
      // ...  
    }

    function getEV(target) {
      // ...  
    }

    function sendAndReceive(target, rawVS, rawEV, inputData) {
      // ...  
    }

    function logRemark(comment) {
      // ...  
    }

  </script>
</head>
<body bgcolor="#66ddcc">
  <h3>Request-Response Test Harness Page</h3>
  <p><b>Actions:</b></p><p>
  <textarea id="comments" rows="24" cols=63">
  </textarea></p>
  <input type="button" value="Run Tests" onclick="runTests();" /> 
</body>
</html>

工具 UI 代码位于此页面底部的主体元素中,仅由一些文本、用于显示信息的 textarea 元素以及启动测试自动化程序的按钮构成。

该测试工具结构的第一步就是使用脚本元素 src 属性来引用 jQuery 库。jQuery 库是一个开放的 JavaScript 函数源集合,可从 jquery.com 获得。虽然您认为 jQuery 是随着 Web 开发而创建,但该库中包含的函数可使其非常适合轻型请求-响应测试自动化程序。这里所指向的是该库的 1.3.2 版本的本地副本。在进行测试自动化时,使用该库的本地副本比指向远程副本更加可靠。接下来,我使用 $(document).ready jQuery 表达式来确保我的工具可以访问该库,并且确保该工具 DOM 已加载到内存中。

在设置指向要测试的 Web 应用程序的变量 targetURL 后,我将内部逗号分隔的测试用例硬编码为字符串数组 testCaseData。在此,我仅硬编码了两个测试用例,但是在生产环境中,您可能会遇到上百个用例。通常,外部测试用例数据比内部测试用例数据更具优势,因为外部数据更易于修改和共享。但是,因为我在此介绍的技术属于轻型技术,所以内部测试用例数据是合理的设计选择。

测试用例中的第一个字段是用例 ID 号。第二个字段是发送到要测试的应用程序的原始请求数据。第三个字段是预期结果。

我如何知道请求数据的格式?确定 HTTP 请求数据格式的最简单方法,就是使用要测试的应用程序执行初步分析,通过使用 Fiddler 等 HTTP 记录程序检查实际的请求数据。

运行测试

主要的工具控制函数是 runTests。此 runTests 函数使用一流的 try-catch 机制进行初步错误处理。我使用辅助函数 logRemark 显示工具的 textarea 元素的信息。此工具使用帮助程序函数 getVS 和 getEV 获取要测试的 ASP.NET Web 应用程序的当前 ViewState 和 EventValidation 值。这些值由应用程序生成且由 Base64 编码,主要起到状态和安全机制的作用,并且必须作为任何 HTTP POST 请求的一部分进行发送。sendAndReceive 函数执行实际的 HTTP 请求并返回相应的 HTTP 响应。runTests 函数迭代每个测试用例:

for (i = 0; i < testCaseData.length; ++i) {
  logRemark("==========================");
  var tokens = testCaseData[i].split(‘,’);
  var caseID = tokens[0];
  var inputData = tokens[1];
  var expected = tokens[2];
  ...

我使用内置 Split 函数将每个测试用例字符串分为更小的片段。然后,再调用 getVS 和 getEV 帮助程序函数:

logRemark(‘Case ID     : ‘ + caseID); 
logRemark(‘Fetching ViewState and EventValidation’);
var rawVS = getVS(targetURL);
var rawEV = getEV(targetURL);

此主处理循环通过调用 sendAndReceive 函数并检查生成的 HTTP 响应,继续查找关联测试用例预期的值:

var response = sendAndReceive(targetURL, rawVS, rawEV, inputData);
logRemark("Expected    : ‘" + expected + "’");
if (response.indexOf(expected) >= 0)
  logRemark("Test result : **Pass**");
else if (response.indexOf(expected) == -1)
  logRemark("Test result : **FAIL**");
} // main loop

getVS 帮助程序函数依赖 jQuery 库:

function getVS(target) {
  $.ajax({
    async: false, type: "GET", url: target,
    success: function(resp) {
      if (resp.hasOwnProperty("d")) s = resp.d;
      else s = resp;
         
      start = s.indexOf(‘id="__VIEWSTATE"’, 0) + 24;
      end = s.indexOf(‘"’, start);
    }
  });
  return s.substring(start, end);
}

getVS 函数的主要设想就是,向要测试的应用程序发送基本的 GET 请求、提取响应并解析出 ViewState 值。$.ajax 函数可接受匿名函数。Async、type 和 URL 参数应该都可以对自身进行说明。响应 resp 对象的 hasOwnProperty(“d”) 方法主要是一个在 Microsoft .NET Framework 3.5 中提供的安全机制,在这种情况下没有必要使用。

我通过查找该属性的起始位置,然后数过 24 个字符到达 ViewState 值真正开始的位置来提取 ViewState 值。getEV 函数代码与 getVS 代码完全相同,只是 EventValidation 值从初始 id=EVENTVALIDATION 属性的 30 个字符处开始。拥有单独的 getVS 和 getEV 函数后,您就可以灵活把握但需要两个单独的基本请求。另一种方法就是将 getVS 和 getEV 重构到单一的帮助程序函数。

sendAndReceive 帮助程序函数将执行实际的 HTTP 请求并提取生成的响应。此函数首先将原始 ViewState 和 EventValidation 字符串转换为 URL 编码的字符串,然后构建发布到 Web 应用程序的数据:

function sendAndReceive(target, rawVS, rawEV, inputData) {
  vs = encodeURIComponent(rawVS);
  ev = encodeURIComponent(rawEV);
  postData = inputData + ‘&__VIEWSTATE=’ + vs +
    ‘&__EVENTVALIDATION=’ + ev;
  ...

对于发布数据中为非法值的字符,内置 encodeURIComponent 函数将它们编码为转义序列。例如,“/”字符编码为 %2F。在记录消息后,sendAndReceive 将使用 $.ajax 方法创建 HTTP POST 请求:

logRemark("Posting " + inputData);
$.ajax({
  async: false,
  type: "POST",
  url: target,
  contentType: "application/x-www-form-urlencoded",
  data: postData,
  ...

之所以创建 $.ajax 方法,主要用于发送异步 XML HTTP 请求,但是将 async 参数设置为 false,该方法也可用于发送标准的同步请求。太巧妙了!您可将内容类型参数值看作奇妙的字符串,只是代表从 HTML 表单元素发布的数据。在获取关联的 HTTP 响应时,sendAndReceive 函数使用与 getVS 相同的模式:

success: function(resp, status) {
      if (resp.hasOwnProperty("d")) s = resp.d;
      else s = resp;
    },
    error: function(xhr, status, errObj) {
      alert(xhr.responseText);
    }
  });
  return s;
}

我还使用可选错误参数在警告框中显示任何致命错误。

关于此测试工具,最后要介绍的功能是 logRemark 实用程序:

function logRemark(comment) {
  var currComment = $("#comments").val();
  var newComment = currComment + "\n" + comment;
  $("#comments").val(newComment);
}

我使用 jQuery 选择器和链接语法获取 textarea 元素中的当前文本,其中具有评论 ID。“#”语法用于按照 ID 选择 HTML 元素,并且 val 函数的作用等同于值 setter 和 getter。我在现有注释文本中附加了一个 comment 参数值和换行字符,然后使用 jQuery 语法更新 textarea 元素。

替代方案

对于基于浏览器的 JavaScript 语言方法的主要替代方案,我已在本文中进行了介绍,就是使用 C# 等语言创建基于 shell 的工具。与基于 shell 的方法比较,基于浏览器的方法最适合于高动态的环境,因为测试自动化程序的生命期很短。此外,此处介绍的基于浏览器的方法独立于平台。此项技术将适用于任何支持 jQuery 库和 JavaScript 的浏览器和 OS 组合。

James McCaffrey 博士 供职于 Volt Information Sciences Inc.,在该公司他负责管理面向在 Microsoft 工作的软件工程师的技术培训。他参与过多项 Microsoft 产品的研发工作,其中包括 Internet Explorer 和 MSN Search。McCaffrey 博士是《.NET 软件测试自动化之道》(Apress, 2006) 一书的作者。您可以通过电子邮件 jmccaffrey@volt.comv-jammc@microsoft.com 与他联系。