使用工作流基础和 Visual Studio 2008 进行测试自动化






4.90/5 (25投票s)
如何为测试流程采用 Windows Workflow Foundation。让我们在可视化图上设计测试并自动执行它!
引言
在本文中,我将展示如何为测试采用 Windows Workflow Foundation (WF)。最初,Microsoft 的 WF 旨在描述应用程序中的业务流程。其理念是,我们也可以利用这项技术来描述测试流程。测试人员通过可视化图设计测试,而且更重要的是,他可以自动执行此测试。
阅读本文后,您将了解
- 如何使用工作流基础自动执行测试
- 如何自动检查测试结果
- 如何在您开发的其他测试中重用测试
- 如何扩展工作流基础以满足您特定的测试需求
为了演示所有“操作方法”,我用 C# 创建了 TestflowFramework 并将其附加到本文。我预计您对 WF 和 C# 有足够的了解,能够说出“你好,世界”。其余内容我将在下面解释。
使用 TestflowFramework 测试器,我们可以在 Visual Studio 2008 中通过图表设计测试(为简洁起见,我将包含测试工作流的图表命名为“Testflow”)。Testflow 图上的块对应于用户的操作,例如,“在字段中键入文本”或“单击按钮”。图表完成后,测试人员可以运行它,并且所有操作都将自动模拟到应用程序上。
本文附带的解决方案包含一个示例应用程序及其测试流程。示例应用程序管理用户列表。它支持查看/添加/编辑/删除用户。示例测试流程执行以下一系列操作:单击“添加用户”,输入“登录名”、“全名”、“部门”字段,依此类推。在接下来的章节中,我将解释如何重用此测试并多次以不同参数运行它。
本文重点介绍测试 Windows Forms 应用程序。类似地,这种方法也可以扩展到 ASP.NET。甚至可以将其应用于 Compact Framework 和 XNA 平台,但需要付出一些额外的努力。
工作原理
好吧,Windows Workflow Foundation 是一项非常强大的技术,与 Visual Studio 2008 结合使用,使其威力无穷。Workflow Foundation 在运行时和设计时都具有完全可扩展的架构。
在运行时,我们需要模拟用户操作并将其应用于 Windows Forms。TestflowFramework 的当前版本实现了几个活动:ClickButtonActivity
、InputTextActivity
和 CloseFormActivity
。任何人都可以扩展此集合并添加一些高级活动来操作网格、查看器等。
对于设计时,我开发了设计器类,用于调整 WF 组件的样式、标题,并强制执行一些限制。特别是,任何测试操作都只能添加到 FormScope
活动中。
FormScope
活动是一个容器元素,它托管任何测试活动并为其提供上下文。换句话说,FormScope
包含用户可以在给定表单上执行的操作。将 FormScope
放置在图表上后,测试人员会选择应用程序中存在的表单。请参见下图
下图显示了绑定到 AddUserForm
的 FormScope
上的 ClickButtonAction
。请注意,所单击的按钮是从一个强类型列表中选择的,该列表通过反射从 AddUserForm
读取。
还值得一提的是,操作模拟是通过反射完成的。这种方法有利有弊。首先,如果您更改表单的分辨率、坐标和布局,尽管如此,测试仍然会正常工作,因为测试绑定到表单和控件的编程名称。这带来了第二个优点:当您重命名/删除控件时,编译器会通知您(是的,在编译时!)。缺点是您可能需要为某些控件开发自定义测试活动。但回报是测试的高可靠性。尽管如此,这不是唯一的方法。某些情况可能需要另一种方法(例如,如果您想测试 Win32 应用程序)。
为应用程序编写测试
该示例包含一个管理简单用户列表的应用程序。它有两个表单
如上所述,我们的测试是嵌套在 FormScope
活动中的一系列用户操作。每个用户操作都由一个专用的活动类型描述。在 Testflowframework 的当前版本中,我包含了四种类型
ClickButtonActivity
- 此活动单击表单上的特定按钮InputTextActivity
- 此活动将指定文本键入TextBox
控件CloseFormActivity
- 此活动关闭当前表单;它等效于单击窗口右上角的关闭按钮ReadProperty
- 实际上,它不直接对应于用户操作;它不是执行某些操作,而是允许读取任何控件属性并将其值保存到工作流的内部属性中,以便后续检查(将在“验证测试结果”章节中详细介绍)
下图显示了添加用户的示例测试流程
在此测试中,单击“添加用户”按钮。然后,使用指定的测试数据填写登录名、全名和部门字段。最终,单击“确定”按钮将提交。
现在,如果您已经掌握了这些基本原理,让我们继续。
重用测试
一旦创建了测试,您就希望使用不同的参数重用它。假设我们有上面的“AddUser”测试。如何创建三个具有不同登录名、全名和部门的用户?我们将使用工作流属性绑定值的方法来完成此任务。请参阅以下两个步骤
- 创建带有参数的测试部分.
- 重用测试部分并设置其参数.
创建“AddUser
”活动。打开其图表并添加一个 InputTextActivity
。对于此活动的 TextBox
属性,选择 txtLogin
并将 InputText
绑定到工作流属性“Login
”。请参见下图
对 FullName 和 Department 执行相同的操作。最后,您应该有三个活动绑定到三个属性。
创建一个新的工作流,并将“AddUser”测试从工具箱中拖放到其中。然后设置 Login、FullName、Department 属性的值。您可以根据需要多次添加此测试以创建任意数量的用户。
验证测试结果
此时,我们的测试已经创建了三个用户。至少它应该如此……我们如何验证这一事实并在结果错误时发出警报?
在这种情况下,我们将简单地验证创建的用户数量:我们将访问用户列表(主表单上的 listUsers
控件)并检查项目数。如果它不等于 3,我们将中止测试。
我开发了一个自定义活动 ReadProperty
,可以重用于读取任何表单上的任何控件的属性,并使其值可用于您任何测试中的后续检查。在这种情况下,我们将从 listUsers
控件读取 Items.Count
属性
如您所见,从应用程序读取数据很简单。我们应该选择我们表单上的控件名称,然后设置该控件的属性名称。最后,我们为目标值指定绑定。因此,listUsers.Items.Count
的值将保存在当前工作流类的 UsersCount
属性中。
在下一步中,我们将 UsersCount
属性与约束进行比较
因此,我们检查条件。如果失败,我们甚至可以指定原因,它将被写入日志文件。相反,如果一切顺利,表单将在短暂延迟后优雅地关闭。
运行测试
当前版本使用一种非常简单的方法来启动测试。要启动测试,您需要使用几个命令行参数来启动应用程序,指定包含测试的测试程序集和测试名称本身。
例如
WinApp.exe -test "TestflowsForWinApp.dll" AddUser_TestFlow TestLog.txt
测试完成后,其结果将被写入日志
31.12.2007 2:01 | Success | AddUsersBatch
31.12.2007 4:18 | Failure | AddUsersBatch. Reason: User Count is Wrong
日志文件名为 TestLog.txt,存储在应用程序文件夹中(例如,对于示例应用程序,它位于 \WinApp\bin\Debug\)。
毫无疑问,这种简化的方法可以根据特定需求进行改进。日志可以保存到 XML 文件中。测试启动引擎可以以更复杂的方式实现。尽管如此,为了突出主要思想,我将其保持得尽可能简单。
提示
- 不要使用标准的
InvokeWorkflow
活动从一个测试调用另一个测试。此活动异步启动指定的流程。在大多数情况下,这是不希望的行为。 - 将可重用的测试部分放在一个单独的程序集中,而不是与主测试程序集放在一起。包含测试部分的主测试程序集应引用该程序集。在这种情况下,您将在工具箱中看到测试部分,并可以轻松地将它们拖放到主测试中。如果测试部分位于同一程序集中,Visual Studio 不会在工具箱中显示它们。
- 您可以使用标准活动来丰富您的测试。例如,使用
DelayActivity
来模拟用户思考。
扩展工作流基础并创建自定义测试活动
现在您知道如何使用 TestflowFramework 创建测试。是时候学习如何设计自己的活动类型并用新的用户操作丰富您的测试了。下面我们将介绍创建 InputTextActivity
的过程,该活动允许将测试文本输入 TextBox
控件
- 创建一个 Activity 类并继承自 WindowsControlActivity。
- 声明 TextBox 属性。
- 实现类型转换器。
- 声明 InputText 属性。
- 实现运行时行为。
public partial class InputTextActivity : WindowsControlActivity
{
}
我们可以继承自最通用的 Activity
类,但是 WindowsControlActivity
为我们提供了测试活动所需的一些基本功能。
根据 Workflow Foundation 的范例,属性应以特殊方式声明,如下所示
public static readonly DependencyProperty TextBoxProperty =
DependencyProperty.Register(
"TextBox",
typeof(string),
typeof(InputTextActivity),
new PropertyMetadata(
"",
DependencyPropertyOptions.Metadata,
new Attribute[]
{ new ValidationOptionAttribute(ValidationOption.Required) }
)
);
[DefaultValue(""),
Description("TextBox where the text will be typed"),
RefreshProperties(RefreshProperties.All),
MergableProperty(false),
TypeConverter(typeof(ControlTypeConverter)),
Category("Testing")]
public string TextBox
{
get
{
return (base.GetValue(TextBoxProperty) as string);
}
set
{
base.SetValue(TextBoxProperty, value);
}
}
每个属性都应附带一个 DependencyProperty
类型的静态字段。此字段充当元数据容器,并帮助 Windows Workflow 为我们提供许多有用的服务。
您可能还会注意到 TypeConverter(typeof(ControlTypeConverter))
属性。这是扩展设计时行为的一个非常重要的点。当在图表上选择我们的 Activity 并且用户尝试在属性窗口中设置 TextBox
属性的值时,ControlTypeConverter
开始工作。
类型转换器向设计器提供值列表,用户可以从下拉列表中选择其中一个。ControlTypeConverter
是一个非常简单的类,您可以在以下代码片段中看到它
protected class ControlTypeConverter : TypeConverter
{
// Methods
public ControlTypeConverter()
{ }
public override TypeConverter.StandardValuesCollection
GetStandardValues(ITypeDescriptorContext context)
{
IControlFieldProvider provider = null;
object[] instance = context.Instance as object[];
if ((instance != null) && (instance.Length > 0))
{
provider = instance[0] as IControlFieldProvider;
}
else
{
provider = context.Instance as IControlFieldProvider;
}
ICollection values = new object[0];
if (provider != null)
{
values = provider.GetPropertyValues(context);
}
return new TypeConverter.StandardValuesCollection(values);
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{ return true; }
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{ return true; }
}
类型转换器中使用的接口 IControlFieldProvider
由 WindowsControlActivity
类实现。它通过反射读取所有表单字段。然后,如果字段类型为 Control
,它会将字段名放入值列表中
protected Type filterType = typeof(Control);
ICollection IControlFieldProvider.GetPropertyValues(ITypeDescriptorContext context)
{
StringCollection strings = new StringCollection();
if (this.FormType != null)
{
BindingFlags flags =
BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.GetField;
foreach (FieldInfo info in this.FormType.GetFields(flags))
{
if (!info.IsSpecialName)
{
if (info.FieldType == filterType || info.FieldType.IsSubclassOf(filterType))
strings.Add(info.Name);
}
}
}
return strings;
}
遵循此模式,您可以声明任何具有任何预定义值的属性。
现在是时候移动到第二个属性 InputText
了。此属性没有预定义值,可以接受任何字符串值。但它还有另一个有用的功能:它是可绑定的。首先,我们必须准备可绑定属性的基础结构。为此,我们声明一个特殊属性
public static readonly DependencyProperty ParametersProperty =
DependencyProperty.Register(
"Parameters",
typeof(WorkflowParameterBindingCollection),
typeof(InputTextActivity),
new PropertyMetadata(
DependencyPropertyOptions.Metadata |
DependencyPropertyOptions.ReadOnly
)
);
public WorkflowParameterBindingCollection Parameters
{
get
{
return ((WorkflowParameterBindingCollection)
(base.GetValue(ParametersProperty)));
}
}
...并在构造函数中用以下行初始化它
base.SetReadOnlyPropertyValue(ParametersProperty,
new WorkflowParameterBindingCollection(this));
绑定基础结构已准备就绪。声明属性
public static DependencyProperty InputTextProperty =
DependencyProperty.Register(
"InputText",
typeof(string),
typeof(InputTextActivity)
);
[Description("Text entered into the text box")]
[Category("Testing")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string InputText
{
get
{
return ((string)(base.GetValue(InputTextProperty)));
}
set
{
base.SetValue(InputTextProperty, value);
}
}
嗯,这一点很简单。在一般情况下,我们将覆盖标准的 Execute
方法。但这已经在父类 WindowsControlActivity
中完成。该类执行一些准备例程,这些例程对于在测试期间正确处理窗口控件是必需的。它还定义了应由继承者覆盖的 DoControlActivity
方法。我们就是这样做
protected override void DoControlActivity(Form form)
{
TextBox box = GetControl(form, TextBox) as TextBox;
box.Select();
box.Text = InputText;
}
我们的测试活动选择指定的 TextBox
并在此处“键入”文本。
底线
恭喜!我们扩展了 Workflow Foundation 并使我们的测试活动生效。
我们声明了一个继承自 WindowsControlActivity
(或 Activity
)的类。然后我们声明了两个属性:第一个属性有一个预定义的控件列表,第二个属性是一个可绑定的字符串属性。所有声明都是按照 WF 所需的特定模式进行的。最后,我们通过覆盖 DoControlActivity
方法(或者如果需要执行通用操作,则通过 Execute
方法)来定义运行时行为。