目录
介绍
目标
测试方案
功能测试(黑匣子)
测试示例
设计
记录测试并生成测试方法
更新测试方法并构建
测试用例规范
测试方法
初始化方法
实现自定义TestContext
测试配置
关于CodedUI的常见问题
不要手动更改* .Designer.cs文件中的代码
经常的异常
NullReferenceException
无法找到UI控件
结论
参考文献
- 下载AvalonEdit_05_03.CodedUITest.zip
- 下载AvalonEdit_05_03.zip
可以通过编码准则和自动测试来确定软件的质量。通常会有单元测试以确保小型单元(例如methods)的质量符合预期。在足够大的项目中,还有其他自动化测试,例如用户界面(UI)测试,以确保用户体验符合预期。
本文是关于用户界面(UI)测试的自动化测试。我们使用Microsoft最新的测试环境CodedUI作为示例来说明如何将测试自动化集成到软件开发迭代中。我们在本文撰写期间发现CodedUI将在Visual Studio 2019结束时被弃用,尽管距离2019年文章的发布日期尚有一段距离,但也有必要指出许多讨论的原则这里也可以应用于其他UI测试框架。
HTML的UI测试自动化已经存在了一段时间。尽管像Selenium这样的开放源代码测试环境已成为最新技术,但还有其他开放源代码项目(如Appium)可用于测试WPF应用程序。
本文列出了开发任何自动UI测试时应实现的重要实践。我们解释了CodeUI测试框架的基本功能和用例,并以此来说明可以扩展方法和属性,从而可以自动化地对测试目标的先前不可测试的部分进行测试。
使用Microsoft的CodedUI的重要先决条件是您已安装Visual Studio Enterprise。
目标UI测试自动化与常规软件开发有很多相似之处,但也具有独特的属性,使其与其他类型的开发区分开。我们使用本节来讨论在实际项目中使用UI自动化时应遵循的最佳实践。应该遵守以下高层目标,以确保尽管UI测试自动化付出了额外的努力,仍可以正常进行开发:
- 在WPF应用的早期迭代中引入自动化测试,
- 保持测试代码易于编写和维护,
- 使测试套件易于部署和运行。
以上几点可以通过自动化测试的重要原理进行扩展。这些原则确保您不仅可以测试任何东西,还可以从项目中最重要的地方开始,并且可以按照自己的方式处理不太重要的事情。这些原则是:
- 从在外部接口上进行测试开始,
- 保持每种测试方法的测试目标简单明了(在测试方法中混合太多逻辑不是一个好主意),
- 请勿修改测试对象或测试环境,
- 减少重复测试
- 无法测试所有组合,
- 选择最有用的组合,
- 避免多余的测试范围,
- 在开发进度与测试进度之间取得平衡。
某些问题(如上述最后一点)解决了重要问题,例如可维护性和自动化UI测试的额外工作。不难看出,具有简单用户界面的简单项目几乎无法证明为进行测试自动化所做的任何额外努力。但是,也有一些项目由大型(去中心化)软件开发人员团队生产,供数百或数千名客户使用。尽管需要付出额外的努力,但这些项目仍可以从测试自动化中获利,因为通过自动化进行的常规测试可以确保UI的所有部分在进入开发周期的最后阶段(修复错误)之前都可以通过基本测试。
测试方案在软件开发过程中可以执行多种测试。这些是:
- 单元测试
- 集成测试
- 功能测试
- 验收测试,
- 性能测试,
- 以及更多[1]。
我们在以下各节中详细介绍了功能测试及其自动化,以说明如何通过代码测试用户界面(UI)。功能测试的开发与软件开发本身具有相似之处,因为我们可以应用最佳实践,模式和经验教训。一种“显而易见的”最佳实践是应用至少涉及两个原理的高级设计原理,并且适用于每种测试:
- 从软件要求开始。全面确定测试要求至关重要,因为这将直接影响测试范围。
- 对于每个测试要求,请始终使用:
- 等价类划分 [2],
- 边值分析 [3],以及
- 推论错误。
全面制作测试用例。
全面的测试案例是从用户需求开始的案例,因为它可以确保用户的可能行为反映在所需的质量中。这可以通过使用列出的模式并花一些时间来推断到目前为止可能尚未评估的错误来确保。
功能测试(黑匣子)功能测试是一种软件测试,可根据规范中的功能要求对软件进行测试。功能测试也称为黑盒测试,因为该测试将被测试的系统视为无法打开以查看其内部的盒子。这种类型的测试主要围绕对系统输入,系统输出和测试期间的行为的期望,而不是查看系统的内部细节。基于故事的功能需求如下所示:
- 用户启动系统,
- 用户更改程序设置,
- 用户单击关闭(按钮),
- 系统保存所有更改的程序设置,并
- 系统停止运行
对此要求的测试将是确定该软件确实保存了所有相关数据并正确关闭了该软件。可以由测试工程师手动执行此测试,但是与手动方法相比,自动化具有许多优势。本节说明如何使用UI Automation for WPF设计和执行自动化功能测试。
此过程通常涉及以下步骤:
步骤
描述
设计
- 确定先决条件
- 确定该软件应执行的功能,以及
- 确定结果。
生成测试方法
使用Microsoft UIAutomation API或以下方式模拟用户操作:
- 模拟用户操作并使用Visual Studio编码的UI工具记录它们。
- 从录制后生成测试代码,并对其进行手动修改以使其更具扩展性。例如,更新方法参数以使其支持数据驱动的测试。
编译并执行
执行测试方法。
检查测试结果
比较实际和预期输出。
Microsoft UI自动化是用于Microsoft Windows的新的可访问性框架。它通过提供对有关用户界面(UI)[4]信息的编程访问来满足辅助技术产品和自动测试框架的需求。
Visual Studio编码的UI [5]封装了UI自动化API,并提供了一个工具(CodedUITestBuilder.exe)来记录用户操作并自动生成测试代码。请注意,Visual Studio编码的UI需要使用Visual Studio Enterprise和编码的UI测试组件进行记录。
在下面的下一部分中,我们将使用上述工具详细介绍自动化功能测试。
测试示例该测试的测试目标通常是一个技术单元:控制,库,或整个软件产品。本节中的测试目标是开源控件AvalonEdit(版本5.0.3)(Github上的源代码),我们设计了一个测试用例以验证其字体设置功能。
设计我们的示例测试用例设计如下:
- 启动AvalonEdit.Sample应用程序,
- 将字体大小设置为指定的有效值(例如:16),
- 验证文本字体大小是否与上一步中设置的相同。
值得注意的是,上述测试用例设计与功能测试部分中确定的工作流程非常相似。这种相似性支持这样的想法,即用户应该由驱动测试目标的软件组件模拟,并且使通常的做法既可以直接实现功能测试用例(作为自动测试),也可以实现给定功能测试的足够接近的变体。
记录测试并生成测试方法在我们的例子中,驱动测试目标的测试组件是CodedUI测试框架。使用CodedUI进行测试记录需要单独的设置和项目类型[5] [6]。
测试步骤的记录和测试方法的生成(使用CodedUITestBuilder.exe)可以通过CodedUI Test Builder接口完成:[5]
可以在CodedUI测试项目中使用上述用户界面来记录并生成实现上述测试用例[5]的测试代码,首先单击“记录/暂停/继续”按钮,然后执行步骤2至3,并通过单击“生成代码”按钮生成测试代码,然后代码应如下所示(最好稍后改进SetFontSize方法以将字体大小作为输入参数):
CodedUITest类
[TestMethod]
public void CodedUITestMethod1()
{
// To generate code for this test, select "Generate Code for CodedUI Test" from the shortcut menu and select one of the menu items.
this.UIMap.SetFontSize();
}
UIMap类
///
/// SetFontSize - Use 'SetFontSizeParams' to pass parameters into this method.
///
public void SetFontSize()
{
WinEdit uIFontEdit = this.UIAvalonEditSampleWindow.UIItemToolBar.UITxtFontEdit;
WinEdit uIFontSizeEdit = this.UIAvalonEditSampleWindow.UIItemToolBar.UITxtFontSizeEdit;
// Type '16' in 'FontSize' text box
uIFontSizeEdit.Text = this.SetFontSizeParams.UIFontSizeEditText;
// Click 'Font' text box
Mouse.Click(uIFontEdit);
}
UIMap文件是支持测试项目中的UI元素和操作的核心文件。测试项目包含至少3种类型的文件:*.uitest,*.cs,和*.designer.cs。*.uitest是一个XML文件。这两个*.uitest和*.designer.cs文件会自动生成(他们不应该手动更改,因为更改将在下次记录操作后会丢失)。因此,要添加自定义代码,应双击该*.uitest文件,然后单击“移动”按钮(在下图中标记为黄色)。
点击移动按钮后,RecordedMethod1位于示例UIMap.cs文件中。我们可以将方法重命名为SetFontSize并添加参数。不用担心,现在CodedUI Test Builder工具不会自动更改代码。请参阅本文后面的常见问题解答部分,以获得更多类似的提示。
然后,当我们记录了初始测试步骤后,就该验证结果了。为此,只需将“添加断言”按钮拖到编辑器区域(编辑器区域将自动标记为蓝色,如下所示),即可添加一个断言方法,以验证AvalonEdit中的文本字体是否已按预期更改。
可能的断言列表显示在“添加断言”工具窗口中。字体大小属性在这里丢失。似乎我们无法通过CodedUI测试生成器获取字体大小,因为AvalonEdit不会公开UIAutomation的font属性。检查AvalonEdit的源代码,使我们发现它实现了ITextRangeProvider接口,但在GetAttributeValue方法中对我们没有任何帮助:
public object GetAttributeValue(int attribute)
{
Log("{0}.GetAttributeValue({1})", ID, attribute);
return null;
}
因此,为了使验证步骤可测试,我们可以改进GetAttributeValue方法以暴露font属性,或者在无法更改源代码的情况下,我们可以实现一个从要测试的控件继承的子类(这里要分的类是TextEditor类)。
应该重写OnCreateAutomationPeer方法以返回自定义AutomationPeer子类,该子类实现相关的模式提供程序以暴露相关的属性。有关在桌面上以编程方式访问UI元素的更多信息,请参考UIAutomation基础知识[4]。
在此示例中,可以按如下所示改进GetAttributeValue方法(请参见GitHub上文章或源代码附带的下载示例:ICSharpCode.AvalonEdit \ Editing \ TextRangeProvider.cs):
public object GetAttributeValue(int attribute)
{
if (AutomationTextAttribute.LookupById(attribute) == TextPatternIdentifiers.FontSizeAttribute)
{
return this.textArea.FontSize;
}
else
{
if (AutomationTextAttribute.LookupById(attribute) == TextPatternIdentifiers.FontNameAttribute)
return this.textArea.FontFamily.Source;
else
return null;
}
}
现在,我们准备重建AvalonEdit.Sample,再次拖动assert按钮,我们将发现该Font属性获得了当前文本的字体名称值。仍然没有FontSize属性。微软提供了一种创建可支持特定用户界面的CodedUI测试框架自定义扩展的方法[7]。但这很难调试,更糟糕的是,Microsoft不赞成这样做。因此,此处不建议使用此解决方案。
实际上,使用UIAutomation可以直接访问FontSize属性。返回到CodedUI测试项目中的FontSize示例,并手动添加以下验证方法以实现FontSize属性声明:
public void SetFontSize(double fontSize)
{
this.UIAvalonEditSampleWindow.UIItemToolBar.UITxtFontSizeEdit.Text = fontSize.ToString();
Mouse.Click(this.UIAvalonEditSampleWindow.UIItemToolBar.UITxtFontEdit);
}
public void AssertMethod1(double expectedFontSize)
{
WpfControl uiTestControl = this.UIAvalonEditSampleWindow.UITextEditorDocument.UIItemDocument;
AutomationElement automationElement = uiTestControl.NativeElement as AutomationElement;
if (automationElement.TryGetCurrentPattern(TextPattern.Pattern, out object pattern))
{
object obj = ((TextPattern)pattern).DocumentRange.GetAttributeValue(TextPattern.FontSizeAttribute);
double actualFontSize = Convert.ToDouble(obj);
Assert.AreEqual(expectedFontSize, actualFontSize, "Font size verified.");
}
Assert.Fail("Assert fail message.");
}
当满足所有前提条件时,SetFontSize方法可用于更改测试目标的字体大小。在另一方面AssertMethod1验证给定的字体大小当前是否设置与否,并在如果不是的情况下抛出Assertion failed。可以在下一部分中看到的相似模式可以用于在测试方法中实现FontSize get / set方法。
更新测试方法并构建测试用户交互和验证正确的结果可以通过两种单独的方法来实现。然后,可以使用以下代码来实现完整的测试方法:
[TestMethod]
public void SetFontSizeTest()
{
double expectedFontSize = 16;
this.UIMap.SetFontSize(expectedFontSize);
this.TestContext.WriteLine($"Step 1: set the font size to {expectedFontSize.ToString()}.");
double actualFontSize = this.UIMap.GetFontSize();
this.TestContext.WriteLine($"Step 2: get the current font size of the TextEditor: {actualFontSize}.");
Assert.AreEqual(expectedFontSize, actualFontSize, "");
this.TestContext.WriteLine("Step 3: confirm the current font size of the TextEditor is same as set in step 1.");
}
现在,我们可以使用Visual Studio测试平台(通过IDE)或通过命令行来构建和运行测试方法,并且测试应该成功执行。可以在Visual Studio中查看测试结果:
带有TestMethod属性标记的方法可以自动识别为测试用例。可能还有其他与测试方法有关的实例或方法。这些可能是初始化或建立在执行期间使用TestContext的前提条件所必需的。本节说明了初始化方法,并给出了实现自定义TestContext的示例。
初始化方法初始化方法可以在不同的执行级别上实现。当前,按照自上而下的粒度顺序,下面列出了4种初始化方法:
- AssemblyInitialize:在所有测试方法之前,在与测试方法相同的程序集中仅执行一次,
- ClassInitialize:在所有测试方法之前,在与测试方法相同的类中仅执行一次,
- Constructor:每次在执行测试方法之前执行一次(如果测试方法是实例方法,则首先创建一个新实例)
- TestInitialize:每次在执行测试方法之前执行。
每个初始化方法都有一个清除方法(一种Dispose方法可以视为构造函数的清除方法),并以相反的顺序执行。
实现自定义TestContext以下是一个示例,展示了一种实现和使用自定义TestContext来替换默认值的方法。
public class CustomTestContext : TestContext
{
private TestContext testContext;
public CustomTestContext(TestContext testContext) { this.testContext = testContext; }
public override void WriteLine(string format, params object[] args)
{
// Perform custom behavior such as re-direct write stream.
}
public override DataRow DataRow => this.testContext.DataRow;
}
下一个代码示例显示了如何在测试中使用TestContext实例。测试类实现的属性类型为'TestContext',名称为'TestContext',因此名称和类型均不能更改,因为属性设置器会在实际测试执行之前通过MSTest Framework自动执行。
[TestClass]
public class Test
{
[TestMethod]
public void TestMethod1()
{
}
private CustomTestContext testContext;
public TestContext TestContext
{
get
{
return this.testContext;
}
set
{
this.testContext = new CustomTestContext(value);
}
}
}
任何测试都可能受到许多全局变量的影响,我们已经在这里看到了如何在测试中使用TestContext。下一节简要介绍了测试配置文件,这些文件也应该影响全局级别的测试。
测试配置Visual Studio测试平台中有两种类型的配置文件:
- .runsettings 和
- .testsettings
.runsettings文件用于配置单元测试[8]而.testsettings文件由早于VS 2019的Visual Studio版本使用。可以引用旧的.testsettings文件,并与.runsettings文件一起使用,如下所示:
my.testsettings
true
关于CodedUI的常见问题
不要手动更改* .Designer.cs文件中的代码
CodedUI测试生成器生成了3个文件。这些文件的扩展名是:
- * .uitest,
- * .cs和
- * .Designer.cs。
每次录制后,*.Designer.cs文件都会更新。因此,您不应更改它,因为Visual Studio仍然可能会覆盖您的更改。自定义方法应始终移至* .cs文件。您可以通过双击* .uitest文件并单击以下屏幕快照中所示的“移动”按钮来执行此操作:
有2个异常(NullReferenceException和“无法找到UI控件”)通常是由线程问题引起的。本节将说明问题并提出解决方案。
NullReferenceExceptionCodedUI测试以COM(公共对象模型)的单线程单元(STA)模式运行。在这种模式下,应仅从TestMethod线程调用所有回放调用,并且UITestControl不应在TestMethods之间共享。
例如,AssemblyInitialize和ClassInitialize方法是在不同的线程中使用TestMethod执行的静态方法。因此,需要在调用任何UIMap方法之前手动初始化回访环境并创建不同的UIMap实例。通常可以使用以下try模式完成此操作:
[ClassInitialize]
public static void MyClassInitialize()
{
Playback.Initialize();
try
{
UIMap uiMap = new UIMap();
uiMap.DoSomethin();
}
finally
{
Playback.Cleanup();
}
}
无法找到UI控件
此问题的详细错误消息是:“搜索可能在'XXControl'上失败了
此错误可能是由于CodedUI测试生成器中的错误未记录目标控件的正确祖先而引起的。
自定义控件可能具有虚拟化的子级。如果要搜索的控件是“ XXControl”自定义的后代,则将其作为父容器包含可能会解决此问题。”
例如,如果有一个名为A的控件,该控件的子级为B,而B的子级为C。并且CodedUI测试生成器未能检测到正确的树结构并将A确定为C的容器。那么您需要更新手动修复该问题的结构,如下面的代码片段所示:
uiMap.A.C.Container = uiMap.B;
uiMap.B.Container = uiMap.A;
结论
本文试图向读者介绍自动化UI测试的领域。我们建议使用从学习到的教训到将UI测试集成到现有开发周期中的模式,而不是黑魔法。我们还展示了如何通过自原始测试目标派生的自定义测试类来使似乎无法用UI测试测试的属性可测试。
参考文献- 不同类型的测试https://www.atlassian.com/continuous-delivery/software-testing/types-of-software-testing
- 等效类划分https://en.wikipedia.org/wiki/Equivalence_partitioning
- 边界值分析https://en.wikipedia.org/wiki/Boundary-value_analysis
- Microsoft UI自动化https://docs.microsoft.com/zh-cn/windows/desktop/WinAuto/entry-uiauto-win32
- 使用CodedUI测试来测试您的代码https://docs.microsoft.com/zh-cn/visualstudio/test/use-ui-automation-to-test-your-code?view=vs-2019
- UIAutomation基础知识https://docs.microsoft.com/zh-cn/windows/desktop/WinAuto/entry-uiauto-win32
- 第三方控件的CodedUI测试扩展https://devblogs.microsoft.com/devops/coded-ui-test-extension-for-3rd-party-controls-the-basics-explained/
- 使用.runsettings文件配置单元测试https://docs.microsoft.com/zh-cn/visualstudio/test/configure-unit-tests-by-using-a-dot-runsettings-file?view=vs-201