测试运行

用消息来测试 Silverlight 应用程序

James McCaffrey

下载代码示例

我是 Silverlight 的狂热推崇者,在本月的专栏中,我将介绍一个可用于测试 Silverlight 应用程序的方法。

Silverlight 是一个完整的 Web 应用程序框架,于 2007 年首次发布。最新版本 Silverlight 3 是 2009 年 7 月发布的。Visual Studio 2010 提供对 Silverlight 的增强支持,尤其是提供了一个可以轻松设计 Silverlight 用户界面的完全集成可视化设计器。

要了解我将在本文中介绍的内容,最好是看一下应用程序本身。图 1 是一个名为 MicroCalc 的简单但很有代表性的 Silverlight 应用程序。可以看到,MicroCalc 承载于 Internet Explorer 内,当然 Silverlight 应用程序也可以由其他浏览器(包括 Firefox、Opera 和 Safari)承载。

图 1 MicroCalc Silverlight 应用程序
图 1 MicroCalc Silverlight 应用程序

图 2 是一个小型测试工具,它也是 Silverlight 应用程序。


图 2 用于 MicroCalc 的测试工具

在此示例中,已选择了第一个测试用例。在单击标记为“Run Selected Test”的按钮控件时,该 Silverlight 测试工具向待测试的 Silverlight MicroCalc 应用程序发送一条消息,其中包含所选测试用例输入数据。这些测试用例数据包含的指令用于模拟用户向应用程序的输入区域中键入 2.5 和 3.0,选择“Multiply”运算,然后单击“Compute”按钮。

应用程序接受测试用例数据,通过使用检测应用程序的测试代码以编程方式执行自身。经过短暂延迟后,测试工具向被测试的应用程序发送第二条消息,请求应用程序发送一条包含应用程序状态(即结果字段中的值)信息的消息。测试工具接收应用程序产生的消息,确定应用程序中的实际值 7.5000 与测试用例数据中的期望值匹配,然后在工具注释区域中显示“Pass”测试用例结果。

本文假设您基本熟悉 C# 语言,并且假设您没有任何 Silverlight 的使用经验。在本专栏后面几节中,我首先介绍所测试的 Silverlight 应用程序。我将向您介绍创建图 1 所示的基于 Silverlight 的小型测试工具的详细信息,然后说明如何检测应用程序。最后我将介绍其他测试方法。

待测试的应用程序

我们来看看在测试自动化示例中作为目标的 Silverlight MicroCalc 应用程序的代码。我使用 Visual Studio 2010 beta 2 创建 MicroCalc。Silverlight 3 完全集成到 Visual Studio 2010 中,不过,我在此处提供的代码也适用于独立安装了 Silverlight 3 SDK 的 Visual Studio 2008。

我在启动 Visual Studio 后,单击“文件”|“新建”|“项目”。请注意,Silverlight 应用程序是一个 .NET 组件,可以在 Web 应用程序中承载,它本身不是 Web 应用程序。在“新建项目”对话框中,选择 C# 语言模板选项。Silverlight 应用程序还可以使用 Visual Basic 创建,甚至使用新的 F# 语言也可以创建 Silverlight 库。

我选择了默认的 Microsoft .NET Framework 4 库选项和 Silverlight 应用程序模板。Silverlight 包含 .NET Framework 的子集,因此并不是 .NET Framework 4 的所有部件都可用于 Silverlight 应用程序。在填写“名称”(SilverCalc) 和“位置”(C:\SilverlightTesting) 字段后,我单击“确定”按钮。(请注意,SilverCalc 是 Visual Studio 项目名称,而 MicroCalc 是应用程序名称。)

Visual Studio 随后显示“新建 Silverlight 应用程序”对话框向我进行提示,该对话框可能会使 Silverlight 初学者感到困惑。我们来进一步了解一下图 3 中的选项。


图 3 “新建 Silverlight 应用程序”对话框选项

第一项“在新网站中承载 Silverlight 应用程序”在默认情况下处于选中状态。该项指示 Visual Studio 创建两个不同的网页来承载 Silverlight 应用程序。

下一项是包含两个宿主页面的 Visual Studio 项目的名称。项目会添加到 Visual Studio 解决方案中。

对话框中的第三项是包含三个选项的下拉控件:“ASP.NET Web 应用程序项目”、“ASP.NET 网站”和“ASP.NET MVC Web 应用程序项目”。对这些选项的完整讨论不在本文范围之内,但是您至少应了解,最佳通用选项是“ASP.NET Web 应用程序项目”。

对话框中的第四项是用于选择 Silverlight 版本(本例中为 3.0)的下拉控件。单击“确定”后,Visual Studio 就会创建一个空白 Silverlight 网站。

我双击 MainPage.xaml 文件,将基于 XAML 的 UI 定义加载到 Visual Studio 编辑器中。我通过添加 Width 和 Height 特性并更改 Background 颜色特性,修改了顶层 Grid 控件的默认特性:

<Grid x:Name="LayoutRoot"
  Background="PaleGreen" 
  Width="300" Height="300">

默认情况下,Silverlight 应用程序在其宿主页面中占据全部工作区。在本例中,我将宽度和高度设置为 300 像素,使我的 Silverlight 应用程序的大小与 WinForm 应用程序的默认大小一样。我调整了颜色,使 Silverlight 应用程序占据的区域清晰可见。

接下来,我使用 Visual Studio 向我的应用程序添加了标签、三个 TextBox 控件、两个 RadioButton 控件和一个 Button 控件,如图 1 所示。Visual Studio 2010 具有完全集成的设计视图,因此,将控件(如 TextBox)拖动到设计图面上时,会自动生成基础 XAML 代码:

<TextBox Width="99" Height="23" Name="textBox1" ... />

将标签和控件放置到 MicroCalc 应用程序上后,我双击 Button 控件,将其事件处理程序添加到 MainPage.xaml.cs 文件中。在代码编辑器中,我键入下面的 C# 代码,以便向 MicroCalc 提供其功能:

private void button1_Click(
  object sender, RoutedEventArgs e) {

  double x = double.Parse(textBox1.Text);
  double y = double.Parse(textBox2.Text);
  double result = 0;
  if (radioButton1.IsChecked == true)
    result = x * y;
  else if (radioButton2.IsChecked == true)
    result = x / y;
  textBox3.Text = result.ToString("0.0000");
}

我首先捕获作为文本输入 textBox1 和 textBox2 控件的值,然后将这些值转换为双精度类型。请注意,为简化示例,我省略了在实际应用程序中应执行的常规错误检查。

接下来,我确定用户选择了哪个 RadioButton 控件。我必须使用完全限定的布尔表达式:

if radioButton1.IsChecked == true

您可能以为我会使用简写形式:

if radioButton1.IsChecked

我使用完全限定形式,是因为 IsChecked 属性是可以为空的布尔类型,而不是普通布尔类型。

在计算所示的结果后,我将格式化为四个小数位的结果放置到 textBox3 控件中。

MicroCalc 现在已准备就绪,我可以按 F5 键指示 Visual Studio 运行应用程序。默认情况下,Visual Studio 会启动 Internet Explorer,并加载自动生成的关联 .aspx 宿主页面。Visual Studio 通过内置 Web 开发服务器(而不是通过 IIS)运行 Silverlight 测试宿主页面。除了一个 .aspx 测试宿主页面之外,Visual Studio 还生成一个 HTML 测试页面,在 Internet Explorer 中键入它的地址可以手动加载该页面。

测试工具

现在,您已看到了所测试的 Silverlight 应用程序,我来介绍一下测试工具。

我决定使用本地消息传递在工具与应用程序之间发送消息。首先启动 Visual Studio 2010 的一个新实例。使用上节所述的过程,创建一个名为 TestHarness 的新 Silverlight 应用程序。与 MicroCalc 应用程序一样,我编辑顶层 Grid 控件,将其默认大小更改为 300x300 像素,并将其背景色更改为 Bisque,以便清楚地突出显示 Silverlight 控件。接下来,我向工具设计图面添加一个 Label 控件、两个 ListBox 控件和一个 Button 控件。

在将 Button 控件的 Content 属性更改为“Run Selected Test”后,我双击该按钮以生成其事件处理程序。在将逻辑代码添加到处理程序之前,我在测试工具的 MainPage.xaml.cs 文件中声明了一个类作用域 LocalMessageSender 对象和测试用例数据,从而使测试工具可以向所测试的应用程序发送消息:

public partial class MainPage : UserControl {
  LocalMessageSender lms = null;
  private string[] testCases = new string[] {
    "001:2.5:3.0:Multiply:7.5000",
    "002:8.6:2.0:Divide:4.3000"
  };
...

LocalMessageSender 类包含在 System.Windows.Messaging 命名空间中,因此我在 .cs 文件顶部使用 using 语句添加了对该类的引用,这样,我不必完全限定类名称。我对测试用例数据使用简单方法,并使用一个以冒号分隔的字符串,其中包含对应于测试用例 ID、第一个输入值、第二个输入值、运算和期望值的字段。接下来,我为每个测试用例字段添加类作用域字符串变量:

private string caseID;
private string input1;
private string input2;
private string operation;
private string expected;
...

实际上,这些变量不是必需的,只是使测试代码更易于阅读和修改。

现在,我将 LocalMessageReceiver 对象实例化为 MainPage 构造函数,从而使测试工具可以从所测试的应用程序接受消息:

public MainPage() {
  InitializeComponent();

  try {
    LocalMessageReceiver lmr =
      new LocalMessageReceiver("HarnessReceiver",
        ReceiverNameScope.Global,
        LocalMessageReceiver.AnyDomain);
...

LocalMessageReceiver 对象构造函数接受三个参数。第一个参数是用于标识接收方的名称(这由 LocalMessageSender 对象用于指定作为目标的接收方)。第二个参数是枚举类型,指定接收方名称的作用域是全局域还是局部域。第三个参数指定接收方从何处(在本例中为任何域)接受消息。

接下来,我关联接收方的事件处理程序,然后启动接收方对象:

lmr.MessageReceived += HarnessMessageReceivedHandler;
lmr.Listen();
...

在本例中,我要求在测试工具接收到消息时,应将控制权传递给一个由程序定义的方法 HarnessMessageReceivedHandler。可以预见,Listen 方法继续监视从所测试应用程序中的 LocalMessageSender 发送的传入消息。

现在,实例化在前面声明的发送方对象:

lms = new LocalMessageSender(
  "AppReceiver", LocalMessageSender.Global);
lms.SendCompleted += HarnessSendCompletedHandler;
...

请注意,发送方对象的第一个参数是目标接收方对象的名称,而不是发送方的标识名称。在本例中,我的测试工具发送方只向所测试应用程序中名为 AppReceiver 的接收方发送消息。换句话说,接收方对象有名称,并会从任何发送方对象接受消息,但是发送方对象没有名称,只向特定接收方发送消息。

在实例化发送方对象后,关联 SendCompleted 事件的事件处理程序。现在,可以加载我的测试用例,并处理所有异常:

...
    foreach (string testCase in testCases) {
      listBox1.Items.Add(testCase);
    }
  } // try
  catch (Exception ex) {
    listBox2.Items.Add(ex.Message);
  }
} // MainPage()

我只是循环访问测试用例数组,将每个测试用例字符串添加到 listBox1 控件。如果捕获到任何异常,则只在用于注释的 listBox2 控件中显示其文本。

此时,测试工具中有一个发送方对象可向应用程序发送测试用例输入,还有一个可从应用程序接受状态信息的接收方对象。现在,回到前面添加的 button1_Click 处理程序方法。在该处理程序中,我首先分析所选测试用例:

string testCaseData = (string)listBox1.SelectedItem;
string[] tokens = testCaseData.Split(':');
caseID = tokens[0];
input1 = tokens[1];
input2 = tokens[2];
operation = tokens[3];
expected = tokens[4];
...

现在,我已准备就绪,可将测试用例输入发送到所测试的 Silverlight 应用程序:

string testCaseInput = 
  input1 + ":" + input2 + ":" + operation;
listBox2.Items.Add("========================");
listBox2.Items.Add("Test case " + caseID);
listBox2.Items.Add(
  "Sending ‘" + testCaseInput + "’ to application");
lms.SendAsync("data" + ":" + testCaseInput);
...

我只是将测试用例输入拼接在一起。我不向应用程序发送测试用例 ID 或预期值,因为只有测试工具才处理这些值。在向 listBox2 控件显示某些注释后,我使用 LocalMessageSender 对象的 SendAsync 方法发送测试用例数据。我在前面加上字符串“data”,从而使应用程序可以识别所接收的消息的类型。

按钮事件处理程序最后会暂停一秒,以便有时间执行应用程序,然后我发送一条消息,向应用程序询问其状态信息:

System.Threading.Thread.Sleep(1000); 
  lms.SendAsync(“response”);
} // button1_Click

回想一下,我曾关联了针对发送完成的事件处理程序,但是在此设计中,我无需执行任何明确的发送后处理。

测试工具代码的最后部分处理从 Silverlight 应用程序发送到测试工具的消息:

private void HarnessMessageReceivedHandler(object sender,
  MessageReceivedEventArgs e) {

  string actual = e.Message;
  listBox2.Items.Add(
    "Received " + actual + " from application");
  if (actual == expected)
    listBox2.Items.Add("Pass");
  else
    listBox2.Items.Add("**FAIL**");

  listBox2.Items.Add("========================");
}

这里,我从应用程序提取消息(即 textBox3 结果控件中的值),将该值存储到名为 actual 的变量中。在显示注释后,我将应用程序发送的实际值与从测试用例数据分析得到的期望值进行比较,以确定并显示测试用例通过/失败结果。

检测 Silverlight 应用程序

现在我们看一下所测试的 Silverlight 应用程序中的检测代码。我首先选择类作用域 LocalMessageSender 对象。

该发送方将向测试工具发送消息:

public partial class MainPage : UserControl {
  LocalMessageSender lms = null;
  public MainPage() {
    InitializeComponent();
...

接下来,我实例化 MainPage 构造函数中的接收方,以便从测试工具接受消息,关联事件处理程序,并开始侦听来自测试工具的消息:

try {
  LocalMessageReceiver lmr =
    new LocalMessageReceiver("AppReceiver",
      ReceiverNameScope.Global,
      LocalMessageReceiver.AnyDomain);
  lmr.MessageReceived += AppMessageReceivedHandler;
  lmr.Listen();
...

和前面一样,请注意,向接收方对象分配了一个名称,此名称对应于测试工具中的发送方对象的第一个参数。然后,我处理所有异常:

...
  }
  catch (Exception ex) {
    textBox3.Text = ex.Message;
  }
} // MainPage()

我在 textBox3 控件(它是 MicroCalc 应用程序结果字段)中显示异常消息。此方法完全是特定的,但如果消息传递代码引发异常,则可能无法向测试工具发送回异常消息。现在,我处理测试工具发送的消息:

private void AppMessageReceivedHandler(object sender,
  MessageReceivedEventArgs e) {
  string message = e.Message;
  if (message.StartsWith("data")) {
    string[] tokens = message.Split(‘:’);
    string input1 = tokens[1];
    string input2 = tokens[2];
    string operation = tokens[3];
...

测试工具发送两个类型的消息。测试用例输入数据以“data”开头,而应用程序状态的请求只是“response”。我使用 StartsWith 方法确定应用程序接收到的消息是不是测试用例输入。如果是,我使用 Split 方法将输入分析为具有描述性名称的变量。

现在,检测使用测试用例输入模拟用户操作:

textBox1.Text = input1;
textBox2.Text = input2;
if (operation == "Multiply")
  radioButton1.IsChecked = true;
else if (operation == "Divide")
  radioButton2.IsChecked = true;
...

一般而言,修改控件的属性(如本示例中的 Text 和 IsChecked 属性)模拟用户输入是非常简单的。不过,模拟按钮单击这类事件需要另一种方法:

button1.Dispatcher.BeginInvoke(
  delegate { button1_Click(null,null); });

Dispatcher 类是 Windows.Threading 命名空间的一部分,因此我向应用程序添加一个引用该类的 using 语句。通过 BeginInvoke 方法可以在 Silverlight 用户界面线程上异步调用方法。BeginInvoke 接受一个委托,该委托是某个方法的包装。我在这里使用匿名委托功能简化调用。BeginInvoke 返回一个 DispatcherOperation 对象,但在本示例中,我可以安全地忽略该值。

Dispatcher 类还有一个 CheckAccess 方法,您可以使用该方法确定是否需要 BeginIvoke(当 CheckAccess 返回 false 时),或者是否可以仅修改属性(CheckAccess 返回 true 时)。

通过处理请求应用程序状态的测试工具消息,完成检测:

...
  }
  else if (message == "response") {
    string actual = textBox3.Text;
    lms.SendAsync(actual);
  }
} // AppMessageReceivedHandler()

如果接收到的消息只是字符串“response”,则获取 textBox3 控件中的值,然后将其发送回测试工具。

您可以使用的方法很多,本文介绍的测试系统只是其中之一,这个方法最适用于 4-4-4 超轻型测试自动化。这里所指的工具的预期生命周期为 4 周或更短,由 4 页或更少的代码组成,需要 4 小时或更少的创建时间。

与其他方式相比,使用消息进行测试的主要优点在于,这种方法非常简单。主要缺点在于,所测试的应用程序必须进行大量检测,而这并不总是可行。

除了前面介绍的方法之外,还可以采用两种重要的方法:一种是使用 HTML Bridge 和 JavaScript,另一种是使用 Microsoft UI 自动化库。同样,我还是会提醒您,没有任何一种特定测试方法适用于所有情况,但是,在许多软件开发方案中,本文介绍的基于消息的方法都是高效有效的方法。

James McCaffrey 博士 供职于 Volt Information Sciences, Inc.,他在公司负责管理对华盛顿州雷蒙德市沃什湾 Microsoft 总部园区的软件工程师进行的技术培训。他参与过多项 Microsoft 产品的研发工作,包括 Internet Explorer 和 MSN Search。Dr. McCaffrey 是《.NET 软件测试自动化之道》(Apress,2006)的作者。可通过 jammc@microsoft.com 与他联系。

衷心感谢以下技术专家对本文的审阅:Karl Erickson 和 Nitya Ravi