工资不涨,物价在疯狂的涨!

博客园 首页 新随笔 联系 订阅 管理
使用 Windows PowerShell 实现 Web UI 自动化
Dr. James McCaffrey

代码下载位置: TestRun2008_03.exe (155 KB)
Browse the Code Online
Windows PowerShellTM 是一种新型 Microsoft 命令外壳和脚本语言,可用作多种轻型测试自动化的平台。在本月的“测试运行”专栏中,我将向您展示如何使用 Windows PowerShell 通过自动化 Internet Explorer® 为所有类型的 Web 应用程序创建快捷简便的 UI 测试自动化。本专栏主要针对初学者,但是经验丰富的工程师也能在此找到一些感兴趣的信息。
开始前,请确保已将要测试的站点添加到 Internet Explorer 中的“受信任的站点”列表中 — 否则脚本可能不起作用。通过发出以下 Windows PowerShell 命令开始自动化演示:
PS C:\> $ie = new-object -com "InternetExplorer.Application"
它会针对 SHDocVw.dll 库中典型的 InternetExplorer COM 自动化对象创建一个实例。new-object 关键字是一个 Windows PowerShell cmdlet(发音为 command-let)。共有约 130 个 cmdlet,它们构成了 Windows PowerShell 功能的核心。可通过运行 get-command 获取 cmdlet 列表,并始终可通过运行 get-help 命令获取有关 cmdlet 的帮助。我向 new-object 提供了一个 -com 开关(实际上是 –comObject 的快捷方式)。它指定使用对象的 ProgID 来实例化典型的 COM 对象,而不是实例化托管对象。
将生成的对象存储到 $ie 变量中(所有 Windows PowerShell 变量前面都有一个 $ 字符,使其易于与其他令牌类型区分开来)。接下来,使用 Navigate 方法将待测试的虚拟 MiniCalc Web 应用程序加载到浏览器自动化对象中:
PS C:\> $ie.navigate("http://localhost/MiniCalc/Default.aspx")
PS C:\> $ie.visible = $true
Windows PowerShell 的其中一个强大功能是可帮助您查找对象的功能。使用旧的脚本技术时,如果不知道 InternetExplorer 对象具有一个 Navigate 方法,就必须使用某些类型的外部引用来查找该信息。但是,如果使用 Windows PowerShell,就有多种快速方法来查看对象的可用方法和属性。例如,键入“$ie.”然后按 Tab 键即可使用 tab 键完成功能。每按一个键,就会显示一个可用属性或方法。也可使用 get-member cmdlet 来获取所有可用属性和方法及其签名的列表:
PS C:\> $ie | get-member | more
另一 Windows PowerShell 发现功能是命令完成。例如,如果键入“$ie.vi”然后按 Tab 键,Windows PowerShell 将为我键入 ie.visible 语句。Windows PowerShell 中的这些发现功能可节省大量时间。
接下来,获得对待测试的应用程序上的所有输入控件的引用:
PS C:\> $doc = $ie.document
PS C:\> $tb1 = $doc.getElementByID("TextBox1")
PS C:\> $tb2 = $doc.getElementByID("TextBox2")
PS C:\> $add = $doc.getElementByID("RadioButton1")
PS C:\> $btn = $doc.getElementByID("Button1")
使用 Document 属性提取活动文档,然后使用 getElementById 方法来获得对每个控件的引用。请注意,要使此技术起作用,所有 HTML 元素都必须具有一个 ID 值。在大多数情况下这都不成问题。如果用 Visual Studio® 创建 Web 应用程序,则所有控件会自动接收 ID。如果为元素并无 ID 的 Web 应用程序编写测试自动化,也可使用 getElementsByTagName 方法来返回元素集合,然后按索引访问特定元素。
接着,自动化模拟一些用户输入:
PS C:\> $tb1.value = 5
PS C:\> $tb2.value = 7
PS C:\> $add.checked = $true
PS C:\> $btn.click()
请注意,尽管 $tb1 和 $tb2 所引用的 TextBox1 和 TextBox2 值是字符串类型,但是可以省略引号,因为 Windows PowerShell 会推断出正确的数据类型,即使是键入如下命令:
PS C:\> $tb1.value = "5"
通过检查 MiniCalc Web 应用程序的最终状态来完成我的交互式自动化:
PS C:\> $tb3 = $doc.getElementById("TextBox3")
PS C:\> $ans = $tb3.value
PS C:\> if ($ans -eq "12.0000") { 'Pass' } else { '*FAIL*' }
首先获得对 TextBox3 控件的引用。之前没有这样做是因为在调用 $btn click 方法后,将向 Web 服务器发送一个 HTTP 请求,生成一个具有新 TextBox3 值的页面并将其返回到客户端浏览器。简便起见,只需键入 'Pass' 而不是 write-host 'Pass',因为字符串值的默认 Windows PowerShell 操作是将值输出到主机。
在本月专栏的以下部分中,我将简要介绍一下待测试的虚拟 MiniCalc Web 应用程序,从而让您知道具体的测试内容。然后,将图 1 所示的交互式命令重构为更实用的 Windows PowerShell 脚本并演示如何处理技巧性的定时问题。最后,通过与其他可选方法(例如使用商用测试框架、用 Visual Studio 编写自定义 C# 测试工具以及编写使用 JavaScript 方法的自定义自动化)进行比较,了解使用 Windows PowerShell 实现 Web UI 自动化的一些优缺点。我相信您会发现我在此展示的技术是对您的软件测试工具包的一个极其有用且有价值的补充。
Figure 1 使用 Windows PowerShell 实现 Web 应用程序 UI 自动化 (单击该图像获得较大视图)

Web 应用程序
让我们来研究一下图 1 背景中所示的 MiniCalc ASP.NET Web 应用程序。它是我的 UI 测试自动化目标。MiniCalc 是一个简单的 ASP.NET Web 应用程序。我在本月专栏中介绍的技术可自动化 ASP.NET Web 应用程序、典型的 ASP Web 应用程序以及使用 PHP 和 Ruby 等技术创建的应用程序。
为使我的 Web 应用程序代码尽可能地简短和简单,我在记事本中编写了应用程序代码并将逻辑代码和显示代码放到一个文件中。完整的代码如图 2 所示。(可从 MSDN® 杂志网站获得本专栏中讨论的所有代码。)我将此 Web 应用程序命名为 Default.aspx 并将应用程序放到网站 http://localhost/MiniCalc 中。
要执行 Web 应用程序 UI 自动化,您必须知道要操作和检查的用户控件的 ID。如果查看了图 2 中的代码,您会发现我的控件使用的是 Visual Studio 样式的默认 ID。对于保存用户所输入整数的两个文本框控件,使用的 ID 是 TextBox1 和 TextBox2:
<asp:TextBox id="TextBox1" width="100" runat="server" />
我使用 RadioButton1 作为单选按钮控件(用于允许用户选择 Addition 或 Multiplication)的 ID;Button1 作为按钮控件(用于让 Web 应用程序将 TextBox1 和 TextBox2 中的值相加或相乘)的 ID,并且使用 TextBox3 作为文本框控件(用于保存结果)的 ID。我的所有逻辑都包含在 Web 应用程序的 Button1_Click 方法(用于处理 Button1 click 事件)中。
首先提取用户输入并将其从字符串类型转换为 int 类型,如下所示:
int alpha = int.Parse(TextBox1.Text.Trim());
int  beta = int.Parse(TextBox2.Text.Trim());
System.Threading.Thread.Sleep(3000);
捕获用户输入后,我插入了一个 Thread.Sleep 语句来模拟一些处理时间(如在访问后端数据库的实际 Web 应用程序中可能出现的处理时间一样)。稍后您将看到,处理无法预测的 HTTP 响应时间是为 Web 应用程序编写轻型 UI 测试自动化最困难的部分。
有了需要用户提供的两个整数后,检查用户想要执行的操作(加或乘),计算结果并将其放入 TextBox3 控件中:
if (RadioButton1.Checked) {
  TextBox3.Text = Sum(alpha, beta).ToString("F4");
}
else if (RadioButton2.Checked) {
  TextBox3.Text = Product(alpha, beta).ToString("F4");
}
else
 TextBox3.Text = "Select method"; 
}
将答案隐式转换为双精度类型,并在将结果放入 TextBox3 控件时,通过将“F4”参数用于 ToString 方法格式化为四个小数位。

使用 Windows PowerShell 测试自动化脚本
尽管在许多测试情形下,本专栏第一部分中所介绍的使用 Windows PowerShell 进行交互式测试自动化非常有用,但您经常需要创建测试自动化脚本。图 3 显示了一种测试 MiniCalc Web 应用程序的方法。图 4 显示了运行中的脚本。
Figure 4 使用脚本执行测试自动化 (单击该图像获得较大视图)
如果研究图 4,您会发现在启动 Windows PowerShell shell 后,我先确认 Windows PowerShell 会话的执行策略允许脚本执行,然后再调用脚本。请注意:在 Windows PowerShell 中,必须指定脚本的路径(如果脚本在当前目录中则使用 .\),即使脚本位于当前目录中。
测试脚本的整体结构为:
# file: testScript.ps1
function main
{
  # code
}

function navigateToApp($browser, [string] $url,
 [string] $controlID, [int] $maxDelays,
 [int] $delayTime)
{
  # code
}

main
# end script
调用主函数 main,但由于没有默认的 Windows PowerShell 脚本入口点,因此可将此函数命名为任何名称。定义辅助 navigateToApp 函数后,发出单个语句 main 来启动脚本的执行。主函数的前几行为:
write-host "`nBegin test automation using Windows PowerShell`n"
write-host 'Launching IE'
$ie = new-object -com "InternetExplorer.Application"
$ie.navigate("about:blank")
$ie.visible = $true
[System.Threading.Thread]::Sleep(2000)
头两个 write-host 语句显示在 Windows PowerShell 中如何智能化双引号,因为特定转义序列(如 'n 换行符和以 $ 字符开头的对象引用)是由脚本执行引擎进行评估。单引号分隔的字符串则按字面解释。
如前所述,我使用 new-object cmdlet 来实例化典型的 InternetExplorer COM 自动化对象的实例。然后导航到 about:blank 页面并使浏览器可见。直接调用到 Microsoft® .NET Framework 中来访问 Thread.Sleep 方法并将测试自动化暂停两秒钟。此处有两点需要注意。首先,可直接调用到 .NET Framework 中是 Windows PowerShell 优于其他大部分脚本技术的关键优势。其次,尽管可通过休眠线程来使待测试的 Web 应用程序有机会响应工作,但其实还有更好的方法(稍后我会进行介绍)。
下面几行自动化脚本将 Internet Explorer 浏览器设为已知状态:
write-host "`nResizing IE to 425 x 535"
$ie.height = 535
$ie.width = 425
通常情况下,在执行大多数 Web 应用程序 UI 测试自动化时,将浏览器的特征设为已知状态是个不错的主意,因为这样可更加轻松地查看自动化发现的所有错误。例如,对于作为 SQL Server® 数据存储的前端并具有在列表框控件中显示结果的搜索功能的一个 Web 应用程序。假定该应用程序的逻辑忘记在特定搜索之后清除列表框控件。那么,从列表框中没有结果的标准默认状态开始,测试自动化可能确定 Web 应用程序可正常运行且未发现错误。但是,如果从列表框中已包含一些结果的状态启动自动化,则新结果将与旧结果连接并会发现错误。因此,还应编写允许各种初始状态的脚本,以帮助发现容易受这些变化影响的错误。
接下来,将浏览器指向待测试的 MiniCalc Web 应用程序:
write-host "`nNavigating to MiniCalc Web application"
navigateToApp $ie "http://localhost/MiniCalc/Default.aspx" "TextBox1" 100 80
调用 navigateToApp 帮助函数。可将此调用解释为“将 Internet Explorer 导航到 URL http://localhost/MiniCalc/Default.aspx,然后等到对 ID 等于 TextBox1 的 HTML 元素的引用变为可访问时,在两次尝试访问 TextBox1 之间暂停 80 毫秒(最多进行 100 次尝试)。”确定何时加载应用程序并非小事。粗略的轮流加载 Web 应用程序很简单,但是通常并不太有效:
$ie.navigate("http://localhost/MiniCalc/Default.aspx")
 [System.Threading.Thread]::Sleep(8000)
此方法存在两个问题:一是缺少较好的方法来预测暂停自动化多长时间,二是没有明确的方法来处理待测试的应用程序在分配时间内未加载的情形。navigateToApp 函数解决了这两个问题,如图 5 所示。
navigateToApp 函数实质上是进入延迟循环,在每次迭代时检查指定的用户控件引用是否可用。如果超过整个循环的最大迭代次数,循环也将退出,以防止出现无限循环。
尽管 Windows PowerShell 是基于对象,但仍可将简单对象作为变量引用。您可以看到,navigateToApp 函数使用了局部变量 $numDelays 和 $loaded,但不必显式地将它们声明为局部变量。可在这些变量前面使用 $private:限制符将其显式声明为局部变量,或使用 $global:限制符使这些变量的值可在此函数以外使用。navigateToApp 中的控制逻辑会检查文档对象是否可用,如果不可用,则使用 continue 语句立即退出当前循环迭代并在延迟后再次尝试。如果具有对文档对象的有效引用,则尝试获得对目标元素的引用。
还可使用 Windows PowerShell elseif 控制结构。另外,除使用显式 $loaded 变量外,还可使用 Windows PowerShell break 语句来退出延迟循环。Windows PowerShell 具有一组丰富的控制结构,可通过它们来以各种不同的风格进行编程(包括您所习惯的编程风格),因而可加快您的学习过程。
退出延迟循环后,检查退出循环的原因是否是超过最大延迟数(即意味着从未获得对目标用户控件的引用,并发出信号表明未成功加载 Web 应用程序)。此时,抛出一个通过使用 Windows PowerShell 陷阱机制在主函数中捕获的异常。
完全加载待测试的 Web 应用程序后,获得对所有用户输入控件的引用:
write-host "`nGetting input controls"
$doc = $ie.document
$tb1 = $doc.getElementByID("TextBox1")
$tb2 = $doc.getElementByID("TextBox2")
$add = $doc.getElementByID("RadioButton1")
$btn = $doc.getElementByID("Button1")
如前所述,getElementById 方法要求所有控件都具有一个 ID 属性,但在需要访问没有 ID 的控件的情况下,也可使用 getElementsByTagName 方法。
然后,执行一个快速检查以确保 HTML 元素引用有效:
if ($tb1 -eq $null -or $tb2 -eq $null –or
    $add -eq $null -or $btn -eq $null) {
  write-host "One or more controls are null"
    -backgroundcolor "red" -foregroundcolor "yellow"
}
else {
    write-host "All controls found"
}
如果编写基于 Windows PowerShell 的 UI 测试自动化,在进行错误检查时,是抛出异常还是只使用 write-host cmdlet 来显示一条消息通常是个人的编码风格问题。从概念上讲,抛出异常是更逻辑的方法,但是如果使用 write-host,则可用 –backgroundcolor 和 –foregroundcolor 参数来指定明显的文本。
确认所有用户输入控件都可用之后,就可以很容易地操作它们,如下所示:
write-host "`nSetting TextBox1 to 5"
$tb1.value = 5
write-host "Setting TextBox2 to 7"
$tb2.value = 7
write-host "Selecting 'Addition' operation"
$add.checked = $true
现在,我已准备好模拟将触发到 Web 服务器的发布的用户操作(在本例中是一个按钮单击),然后等待服务器的响应。这并不简单。与加载待测试的应用程序类似,以下的简单方法不是很有效,因为缺少可靠的方法来事先了解暂停测试自动化多长时间:
write-host "`nClicking 'Calculate' button"
$btn.click()
[System.Threading.Thread]::Sleep(5000)
其中一个可能的解决方案是首先获得一些 Web 应用程序的预先请求控件值,接着触发 HTTP 请求,然后使用延迟循环直至预先请求控件值发生更改。例如,首先可以提取 TextBox3 控件中的值并加以保存:
$tb3 = $doc.getElementByID("TextBox3")
$old = $tb3.value
现在模拟用户单击“计算”按钮:
$btn.click()
然后进入延迟循环,直至 TextBox3 中的值发生更改或超过最大延迟数:
$wait = $true
$numWaits = 0
while ($wait -and $numWaits -lt 100) {
  $numWaits++
  [System.Threading.Thread]::Sleep(50)
  $tb3 = $doc.getElementByID("TextBox3")
  if ($tb3.value -ne $old) {
    $wait = $false
  }
  else {
    write-host "Waiting for app to respond $numWaits . . ."
  }
}
延迟循环终止后,检查退出原因是否是超过查找目标控件值发生更改的最大尝试次数:
if ($numWaits -eq 100) {
  throw "Application did not respond after 100 delays"
}
else {
  write-host "Application has responded"
}
此时,我的自动化已成功加载了待测试的 Web 应用程序、操作了元素、触发了一个 HTTP 请求并确定服务器存在响应。现在,可检查 Web 应用程序的最终状态以确定“pass”(通过)或“fail”(失败)结果。首先,获得对 TextBox3 的引用以及其值:
write-host "`nChecking for 12.0000"
$tb3 = $doc.getElementByID("TextBox3")
$ans = $tb3.value
直到现在才能获得对 TextBox3 的引用,因为在 HTTP 请求之前获得的引用会在 HTTP 响应后丢失。现在可以检查结果值并显示测试方案结果:
if ($ans -eq '12.0000') {
  write-host "Target value found"
  write-host "`nTest scenario: Pass" -foregroundcolor 'green'
}
else {
  write-host "Target value not found"
  write-host "`nTest scenario: *FAIL*" -foregroundcolor 'red'
}
在主函数的结尾处,使用 trap 语句处理测试运行期间抛出的任何异常:
trap {
  write-host "Fatal error: " $_.exception.message
  -backgroundcolor red -foregroundcolor yellow
}
在此仅显示异常消息。在某些情况下,可能希望使用 continue 语句来强制测试自动化继续运行(即使发生致命错误也一样)。或者可能希望完全退出自动化。

再进一步
为了清晰起见,我硬编码了许多值,但是您可能希望在生产环境中参数化自动化脚本。例如,我硬编码了待测试的 Web 应用程序的路径。Windows PowerShell 具有将命令行参数传递到脚本的良好机制,因此可通过将 param($param1, $param2) 等添加到脚本顶端来向脚本添加参数。
另外,可能希望通过参数化测试用例输入值和对应预期值来扩展自动化脚本。而且,Windows PowerShell 可从外部纯文本文件、外部 XML 文件、SQL 数据库或其他测试用例数据存储读取测试用例数据。
类似地,我的示例测试自动化脚本将其“pass/fail”(通过/失败)结果写入外壳中。Windows PowerShell 还可将结果轻松地保存到希望的任意数据存储类型中。其中一个非常有趣的例子是将测试结果写入 Microsoft Team Foundation Server,从而可具有强大的测试管理功能。有关与 Team Foundation Server 集成的详细信息,请参阅《MSDN 杂志》2008 年发布的“测试运行”专栏,网址为:msdn.microsoft.com/msdnmag/issues/08/LA/TestRun
与测试自动化是补充而不是取代手动测试一样,使用 Windows PowerShell 实现超轻型软件测试自动化是补充而不是取代其他类型的测试自动化和测试框架。例如,由于在此介绍的 UI 测试技术使用的是 Internet Explorer 对象模型,因此无法用它来测试在其他 Web 浏览器或设备上运行的 Web 应用程序。在此类情况下,可使用 JavaScript 方法(请参阅 2007 年 2 月的“测试运行”专栏“AJAX 测试自动化”,网址为:msdn.microsoft.com/msdnmag/issues/07/02/TestRun)或使用商用测试框架。
此处介绍的技术在某种程度上可用于其他脚本语言。但是,根据我的经验,将 Windows PowerShell 用于基于脚本的测试自动化具有优于其他脚本环境的五个小却很重要的特征。
首先,Windows PowerShell 可直接访问 COM 对象和 .NET Framework(而不是通过包装机制)。
其次,Windows PowerShell 的交互式模式可以在开发自动化脚本的同时快速进行试验,从而大大加快了脚本创建过程。
第三,Windows PowerShell 的内置发现机制(如 dot-tab 完成功能和 get-member cmdlet)可提供实质上是虚拟的文档帮助功能。
第四,Windows PowerShell 内置的 cmdlets 集合简化了许多常见的测试自动化任务。
第五,在我看来,Windows PowerShell 使用非常简单并且更加直观。
尽管这些优点没有一个非常强大,但是将它们综合在一起时,相对于编写自动化所花费的成本而言,使用 Windows PowerShell 实现测试自动化可提供较高的回报率。

请将您想向 James 询问的问题和提出的意见发送至 testrun@microsoft.com.


Dr. James McCaffrey 供职于 Volt Information Sciences, Inc.,负责对华盛顿地区雷蒙德市沃什湾 Microsoft 总部园区内工作的软件工程师的技术培训进行管理。他参与过多项 Microsoft 产品的工作,其中包括 Internet Explorer 和 MSN 搜索。James 是《.NET 软件测试自动化之道》一书的作者。您可以通过电子邮件 jmccaffrey@volt.comv-jammc@microsoft.com 与他联系。
posted on 2009-04-09 11:16  腾云驾雾  阅读(2091)  评论(1编辑  收藏  举报