彩票走势图

logo DevExpress WinForm中文手册
文档彩票走势图>>DevExpress WinForm中文手册>>自动化UI测试(UI自动化、Appium、编码UI)

自动化UI测试(UI自动化、Appium、编码UI)


立即下载DevExpress WinForms

用户界面 (UI) 测试可验证应用程序的所有视觉元素是否正常运行。UI测试可以由测试人员手动执行,也可以借助自动化测试工具执行,自动化测试更快、更可靠且更具成本效益。

微软编码UI测试(CUIT)框架

编码UI测试框架是微软的一个解决方案,它利用控件的可访问性层来记录和运行UI测试,CUIT组件通过Visual Studio Installer分发。

该解决方案在Visual Studio 2019及以后被宣布过时,在Visual Studio 2022中,您仍然可以运行已编码UI测试,但不能记录新测试,较新的IDE版本将完全放弃对CUIT的支持。

参见:

DevExpress编码UI扩展

DevExpress Coded UI是Microsoft Coded UI Tests的扩展,专为基于DevExpress的应用程序量身定制。这些解决方案之间的区别在于与Microsoft CUIT不同,DevExpress编码UI扩展不利用辅助功能,该框架通过专有通道与控件进行通信,并使用DevExpress控件中声明的帮助程序类。

Microsoft 终止CUIT的决定也会影响DevExpress编码UI扩展,对于较新的项目,我们建议您改用Appium或UI Automation。

也可以看看:

Appium和UI自动化

Appium是一款开源工具,可让您为 Web、混合、iOS 移动、Android 移动和 Windows 桌面平台创建自动化UI测试,要测试Windows应用程序则需要设置。

也可以看看:

  • — Appium 文档。
  • — 带有示例的 DevExpress 博客文章。

Appium(以及多个其他测试框架)利用UI Automation ——Microsoft 的Windows辅助功能框架,您可以直接使用此框架(不涉及任何第三方解决方案)来编写UI测试。

也可以看看:

  • — 来自 Microsoft 的概述文章。

Appium和UI Automation 之间的选择取决于场景和测试要求的复杂性,Appium更容易使用,但也有更多限制,因为它没有实现所有UIA功能。例如,Appium 允许您使用 成员,但只能使用属性,不能使用方法。

提示:调度程序、富编辑器、PDF查看器和电子表格控件目前不支持UI自动化。

步骤记录器和手动测试脚本

大多数测试自动化平台都提供了记录工具,这些工具在运行时跟踪您的操作(光标移动、单击和键盘按键),并生成模拟这些操作的代码。下面的博客文章展示了如何使用Appium步进记录器与DevExpress控件:。

记录器允许您编写更少的代码,但它们可能产生不稳定的测试并导致性能问题。例如,大多数测试记录器在元素选择代码中枚举目标UI元素的所有父元素,因此,一个小的UI修改(比如添加一个新的Panel容器)会导致这个选择代码失败。

为了避免潜在的问题并更好地理解测试的功能,我们建议手动编写测试脚本。例如,您可以选择为目标UI元素检查哪些父控件,而不是列出元素父元素的整个层次结构,或者直接获取该元素而不访问其任何父元素。

如何编写Appium和UI自动化测试

常用测试结构

Appium和UI自动化测试共享类似的代码块层次结构,每个块都由一个 NUnit属性装饰。

修饰包含测试的类。

每次测试即将开始时,都会调用带有此属性的方法。

与SetUp属性相反,此属性修饰每次测试完成时执行的一组指令。

修饰一个包含测试脚本的方法。

Appium和UIA测试的一般实现如下所示:

C#:

using System;
using NUnit.Framework;

namespace VisualTests {
[TestFixture]
public class MyAppTests {
[SetUp]
public void Setup() {
// Actions repeated before each test
}
[TearDown]
public void Cleanup() {
// Actions repeated after each test
}
[Test]
public void Test1() {
// Test #1
}
[Test]
public void Test2() {
// Test #2
}
}
}

VB.NET:

Imports System
Imports NUnit.Framework

Namespace VisualTests
<TestFixture>
Public Class MyAppTests
<SetUp>
Public Sub Setup()
' Actions repeated before each test
End Sub
<TearDown>
Public Sub Cleanup()
' Actions repeated after each test
End Sub
<Test>
Public Sub Test1()
' Test #1
End Sub
<Test>
Public Sub Test2()
' Test #2
End Sub
End Class
End Namespace

检查Tool

要为任何UI元素编写测试,需要做以下事情:

  • 通过ID或名称获取该元素。
  • 检查它支持哪些模式,并利用这些模式的属性和方法来模拟用户操作。
  • 调用 方法来比较实际和预期的控制状态。

要获取元素名称和 ID,并检查其可用的模式 API,请使用Microsoft Inspect —— Windows SDK安装中包含的免费工具。

WPF可及性和测试检查

手工检查UI元素还允许您定位不良的可访问性名称和其他问题,要解决这些问题,请处理DXAccessible.QueryAccessibleInfo事件。

如何编写 Appium 测试

  1. 在 Windows 设置中启用。
  2. 下载、安装并运行 。
  3. 在需要测试的项目中打开全局WindowsFormsSettings.UseUIAutomation。
  4. 在 Visual Studio 中创建一个新的“单元测试项目” 。
  5. 安装“Appium.WebDriver” NuGet 包。
  6. 根据通用测试结构部分创建测试,下面的代码说明了一个自动化测试示例。

C#:

using System;
using System.Windows.Forms;
using NUnit.Framework;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Windows;

namespace AppiumTests {
[TestFixture]
public class EditorsDemoTests {
WindowsDriver<WindowsElement> driver;
string editorsDemoPath =
@"C:\Work\2022.1\Demos.Win\EditorsDemos\CS\EditorsMainDemo\bin\Debug\EditorsMainDemo.exe";
[SetUp]
public void Setup() {
AppiumOptions options = new AppiumOptions();
options.AddAdditionalCapability("app", editorsDemoPath);
driver = new WindowsDriver<WindowsElement>(new Uri("//127.0.0.1:4723"), options);
}
[TearDown]
public void Cleanup() {
driver.Close();
}
[Test]
public void ProgressBarTest() {
var form = driver.FindElementByAccessibilityId("RibbonMainForm");

var progressBarAccordionItem =
form.FindElementByAccessibilityId("accordionControl1").FindElementByName("Progress Bar");
progressBarAccordionItem.Click();
Assert.AreEqual("True", progressBarAccordionItem.GetAttribute("SelectionItem.IsSelected"));
AccessibleStates itemStates =
(AccessibleStates)int.Parse(progressBarAccordionItem.GetAttribute("LegacyState"));
Assert.IsTrue(itemStates.HasFlag(AccessibleStates.Selected));

form.FindElementByName("Position Management").Click();

var minMaxComboBox = form.FindElementByAccessibilityId("comboBoxMaxMin");
minMaxComboBox.Click();
minMaxComboBox.SendKeys(
OpenQA.Selenium.Keys.Down + OpenQA.Selenium.Keys.Down + OpenQA.Selenium.Keys.Enter);
Assert.AreEqual("Min = 100; Max = 200", minMaxComboBox.Text);

var progressBar = form.FindElementByAccessibilityId("progressBarSample2");
Assert.AreEqual("100", progressBar.GetAttribute("RangeValue.Minimum"));
Assert.AreEqual("200", progressBar.GetAttribute("RangeValue.Maximum"));
Assert.AreEqual("100", progressBar.GetAttribute("RangeValue.Value"));
Assert.AreEqual("0%", progressBar.Text);

form.FindElementByName("Step!").Click();
Assert.AreEqual("110", progressBar.GetAttribute("RangeValue.Value"));
Assert.AreEqual("10%", progressBar.Text);
}
}
}

VB.NET:

Imports System
Imports System.Windows.Forms
Imports NUnit.Framework
Imports OpenQA.Selenium.Appium
Imports OpenQA.Selenium.Appium.Windows

Namespace AppiumTests
<TestFixture>
Public Class EditorsDemoTests
Private driver As WindowsDriver(Of WindowsElement)
Private editorsDemoPath As String = "C:\Work\2022.1\Demos.Win\EditorsDemos\CS\EditorsMainDemo\bin\Debug\EditorsMainDemo.exe"
<SetUp>
Public Sub Setup()
Dim options As New AppiumOptions()
options.AddAdditionalCapability("app", editorsDemoPath)
driver = New WindowsDriver(Of WindowsElement)(New Uri("//127.0.0.1:4723"), options)
End Sub
<TearDown>
Public Sub Cleanup()
driver.Close()
End Sub
<Test>
Public Sub ProgressBarTest()
Dim form = driver.FindElementByAccessibilityId("RibbonMainForm")

Dim progressBarAccordionItem = form.FindElementByAccessibilityId("accordionControl1").FindElementByName("Progress Bar")
progressBarAccordionItem.Click()
Assert.AreEqual("True", progressBarAccordionItem.GetAttribute("SelectionItem.IsSelected"))
Dim itemStates As AccessibleStates = CType(Integer.Parse(progressBarAccordionItem.GetAttribute("LegacyState")), AccessibleStates)
Assert.IsTrue(itemStates.HasFlag(AccessibleStates.Selected))

form.FindElementByName("Position Management").Click()

Dim minMaxComboBox = form.FindElementByAccessibilityId("comboBoxMaxMin")
minMaxComboBox.Click()
minMaxComboBox.SendKeys(OpenQA.Selenium.Keys.Down + OpenQA.Selenium.Keys.Down + OpenQA.Selenium.Keys.Enter)
Assert.AreEqual("Min = 100; Max = 200", minMaxComboBox.Text)

Dim progressBar = form.FindElementByAccessibilityId("progressBarSample2")
Assert.AreEqual("100", progressBar.GetAttribute("RangeValue.Minimum"))
Assert.AreEqual("200", progressBar.GetAttribute("RangeValue.Maximum"))
Assert.AreEqual("100", progressBar.GetAttribute("RangeValue.Value"))
Assert.AreEqual("0%", progressBar.Text)

form.FindElementByName("Step!").Click()
Assert.AreEqual("110", progressBar.GetAttribute("RangeValue.Value"))
Assert.AreEqual("10%", progressBar.Text)
End Sub
End Class
End Namespace
  • 上面的代码借助FindElementByName和FindElementByAccessibilityId方法定位所需的UI元素,要获取元素名称或ID,请在Inspect中浏览元素属性
  • 要模拟鼠标单击和按键,请调用Click()和SendKeys方法。
  • 使用UIElement.GetAttribute方法获取模式属性的值,这些名称在Inspect中也可见。

要访问模式的属性LegacyIAccessible,请使用“Legacy{PropertyName}”格式:

C#:

var value = progressBarAccordionItem.GetAttribute("LegacyState");

点击复制

VB.NET:

Dim value = progressBarAccordionItem.GetAttribute("LegacyState")

点击复制

其他模式的属性用“{PatternName}.{PropertyName}”格式访问:

C#:

var value = progressBar.GetAttribute("RangeValue.Maximum");

点击复制

VB.NET:

Dim value = progressBar.GetAttribute("RangeValue.Maximum")

点击复制

  • DevExpress 上下文菜单没有直接所有者,因此它们的可访问对象是桌面窗口的子窗口,而不是应用程序窗口,要访问这些菜单中的项目,请使用桌面窗口驱动程序。

C#:

AppiumOptions globalDriverOptions = new AppiumOptions();
globalDriverOptions.AddAdditionalCapability("app", "Root");
var globalDriver = new WindowsDriver<WindowsElement>(new Uri("//127.0.0.1:4723"), globalDriverOptions);
var menuItem = globalDriver.FindElementByName("ItemName");

点击复制

VB.NET:

Dim globalDriverOptions As AppiumOptions = New AppiumOptions()
globalDriverOptions.AddAdditionalCapability("app", "Root")
Dim globalDriver = New WindowsDriver(Of WindowsElement)(New Uri("//127.0.0.1:4723"), globalDriverOptions)
Dim menuItem = globalDriver.FindElementByName("ItemName")

点击复制

如何编写 UI 自动化测试

  1. 在需要测试的项目中打开全局WindowsFormsSettings.UseUIAutomation属性。
  2. 在Visual Studio中创建一个新的“Unit Test Project”。
  3. 在您的项目中包括UIAutomationClient.dll和UIAutomationTypes.dll库。
  4. 根据公共测试结构部分创建测试,下面的代码演示了一个自动化测试示例。

C#:

using System;
using System.Diagnostics;
using System.Threading;
using System.Windows.Automation;
using Microsoft.Test.Input;
using NUnit.Framework;

namespace UIAutomationTests {
[TestFixture]
public class OutlookInspiredTests {
string path =
@"C:\Work\2022.1\Demos.RealLife\DevExpress.OutlookInspiredApp\
bin\Debug\DevExpress.OutlookInspiredApp.Win.exe";
Process appProcess;
[SetUp]
public void Setup() {
appProcess = Process.Start(path);
}
[TearDown]
public void TearDown() {
appProcess.Kill();
}
[Test]
public void Test1() {
AutomationElement form =
AutomationElement.RootElement.FindFirstWithTimeout(TreeScope.Children, new PropertyCondition(
AutomationElement.AutomationIdProperty, "MainForm"), 10000);

AutomationElement grid =
form.FindFirstWithTimeout(TreeScope.Descendants, new PropertyCondition(
AutomationElement.AutomationIdProperty, "gridControl"), 5000);

AutomationElement cell = FindCellByValue(grid, "FULL NAME", "Greta Sims");
Mouse.MoveTo(cell.GetPoint());
Mouse.DoubleClick(MouseButton.Left);

AutomationElement detailForm =
form.FindFirstWithTimeout(TreeScope.Children, new PropertyCondition(
AutomationElement.AutomationIdProperty, "DetailForm"), 5000);

AutomationElement jobTitleEdit =
detailForm.FindFirstWithTimeout(TreeScope.Descendants, new PropertyCondition(
AutomationElement.AutomationIdProperty, "TitleTextEdit"));
((ValuePattern)jobTitleEdit.GetCurrentPattern(ValuePattern.Pattern)).SetValue("HR Head");

AutomationElement department =
detailForm.FindFirstWithTimeout(TreeScope.Descendants, new PropertyCondition(
AutomationElement.AutomationIdProperty, "DepartmentImageComboBoxEdit"));
((ExpandCollapsePattern)department.GetCurrentPattern(ExpandCollapsePattern.Pattern)).Expand();

AutomationElement managementItem =
detailForm.FindFirstWithTimeout(TreeScope.Descendants, new PropertyCondition(
AutomationElement.NameProperty, "Management"));
((InvokePattern)managementItem.GetCurrentPattern(InvokePattern.Pattern)).Invoke();

AutomationElement saveClose =
detailForm.FindFirstWithTimeout(TreeScope.Descendants, new PropertyCondition(
AutomationElement.NameProperty, "Save & Close"));
((InvokePattern)saveClose.GetCurrentPattern(InvokePattern.Pattern)).Invoke();

AutomationElement jobTitle =
form.FindFirstWithTimeout(TreeScope.Descendants, new PropertyCondition(
AutomationElement.AutomationIdProperty, "sliTitle"));
Assert.AreEqual("HR Head", jobTitle.Current.Name);
}

AutomationElement FindCellByValue(AutomationElement grid, string columnName, string cellValue) {
TablePattern tablePattern = (TablePattern)grid.GetCurrentPattern(TablePattern.Pattern);
AutomationElement[] headers = tablePattern.Current.GetColumnHeaders();
int columnIndex = -1;
for(int i = 0; i < headers.Length - 1; i++)
if(headers[i].Current.Name == columnName)
columnIndex = i;
if(columnIndex == -1)
return null;
for(int i = 0; i < tablePattern.Current.RowCount; i++) {
AutomationElement cell = tablePattern.GetItem(i, columnIndex);
if(cell != null) {
ValuePattern valuePattern = (ValuePattern)cell.GetCurrentPattern(ValuePattern.Pattern);
if(valuePattern.Current.Value == cellValue) {
return cell;
}
}
}
return null;
}
}

public static class AutomationElementExtensions {
public static System.Drawing.Point GetPoint(this AutomationElement @this) {
System.Windows.Point windowsPoint = @this.GetClickablePoint();
return new System.Drawing.Point(Convert.ToInt32(windowsPoint.X), Convert.ToInt32(windowsPoint.Y));
}
public static AutomationElement FindFirstWithTimeout(this AutomationElement @this,
TreeScope scope, Condition condition, int timeoutMilliseconds = 1000) {
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
do {
var result = @this.FindFirst(scope, condition);
if(result != null)
return result;
Thread.Sleep(100);
}
while(stopwatch.ElapsedMilliseconds < timeoutMilliseconds);
return null;
}
}
}

VB.NET:

Imports System
Imports System.Diagnostics
Imports System.Threading
Imports System.Windows.Automation
Imports Microsoft.Test.Input
Imports NUnit.Framework

Namespace UIAutomationTests
<TestFixture>
Public Class OutlookInspiredTests
Private path As String =
"C:\Work\2022.1\Demos.RealLife\DevExpress.OutlookInspiredApp\bin\Debug\DevExpress.OutlookInspiredApp.Win.exe"
Private appProcess As Process
<SetUp>
Public Sub Setup()
appProcess = Process.Start(path)
End Sub
<TearDown>
Public Sub TearDown()
appProcess.Kill()
End Sub
<Test>
Public Sub Test1()
Dim form As AutomationElement = AutomationElement.RootElement.FindFirstWithTimeout(TreeScope.Children, New PropertyCondition(AutomationElement.AutomationIdProperty, "MainForm"), 10000)

Dim grid As AutomationElement = form.FindFirstWithTimeout(TreeScope.Descendants, New PropertyCondition(AutomationElement.AutomationIdProperty, "gridControl"), 5000)

Dim cell As AutomationElement = FindCellByValue(grid, "FULL NAME", "Greta Sims")
Mouse.MoveTo(cell.GetPoint())
Mouse.DoubleClick(MouseButton.Left)

Dim detailForm As AutomationElement = form.FindFirstWithTimeout(TreeScope.Children, New PropertyCondition(AutomationElement.AutomationIdProperty, "DetailForm"), 5000)

Dim jobTitleEdit As AutomationElement = detailForm.FindFirstWithTimeout(TreeScope.Descendants, New PropertyCondition(AutomationElement.AutomationIdProperty, "TitleTextEdit"))
CType(jobTitleEdit.GetCurrentPattern(ValuePattern.Pattern), ValuePattern).SetValue("HR Head")

Dim department As AutomationElement = detailForm.FindFirstWithTimeout(TreeScope.Descendants, New PropertyCondition(AutomationElement.AutomationIdProperty, "DepartmentImageComboBoxEdit"))
CType(department.GetCurrentPattern(ExpandCollapsePattern.Pattern), ExpandCollapsePattern).Expand()

Dim managementItem As AutomationElement = detailForm.FindFirstWithTimeout(TreeScope.Descendants, New PropertyCondition(AutomationElement.NameProperty, "Management"))
CType(managementItem.GetCurrentPattern(InvokePattern.Pattern), InvokePattern).Invoke()

Dim saveClose As AutomationElement = detailForm.FindFirstWithTimeout(TreeScope.Descendants, New PropertyCondition(AutomationElement.NameProperty, "Save & Close"))
CType(saveClose.GetCurrentPattern(InvokePattern.Pattern), InvokePattern).Invoke()

Dim jobTitle As AutomationElement = form.FindFirstWithTimeout(TreeScope.Descendants, New PropertyCondition(AutomationElement.AutomationIdProperty, "sliTitle"))
Assert.AreEqual("HR Head", jobTitle.Current.Name)
End Sub

Private Function FindCellByValue(ByVal grid As AutomationElement, ByVal columnName As String, ByVal cellValue As String) As AutomationElement
Dim tablePattern As TablePattern = CType(grid.GetCurrentPattern(TablePattern.Pattern), TablePattern)
Dim headers() As AutomationElement = tablePattern.Current.GetColumnHeaders()
Dim columnIndex As Integer = -1
For i As Integer = 0 To headers.Length - 2
If headers(i).Current.Name = columnName Then
columnIndex = i
End If
Next i
If columnIndex = -1 Then
Return Nothing
End If
For i As Integer = 0 To tablePattern.Current.RowCount - 1
Dim cell As AutomationElement = tablePattern.GetItem(i, columnIndex)
If cell IsNot Nothing Then
Dim valuePattern As ValuePattern = CType(cell.GetCurrentPattern(ValuePattern.Pattern), ValuePattern)
If valuePattern.Current.Value = cellValue Then
Return cell
End If
End If
Next i
Return Nothing
End Function
End Class

Public Module AutomationElementExtensions
<System.Runtime.CompilerServices.Extension> _
Public Function GetPoint(ByVal this As AutomationElement) As System.Drawing.Point
Dim windowsPoint As System.Windows.Point = this.GetClickablePoint()
Return New System.Drawing.Point(Convert.ToInt32(windowsPoint.X), Convert.ToInt32(windowsPoint.Y))
End Function
<System.Runtime.CompilerServices.Extension> _
Public Function FindFirstWithTimeout(ByVal this As AutomationElement, ByVal scope As TreeScope, ByVal condition As Condition, Optional ByVal timeoutMilliseconds As Integer = 1000) As AutomationElement
Dim stopwatch As New Stopwatch()
stopwatch.Start()
Do
Dim result = this.FindFirst(scope, condition)
If result IsNot Nothing Then
Return result
End If
Thread.Sleep(100)
Loop While stopwatch.ElapsedMilliseconds < timeoutMilliseconds
Return Nothing
End Function
End Module
End Namespace
  • 与Appium测试类似,根据从Inspect复制的名称或id检索元素,使用 来查找所需的元素。
  • 自定义FindFirstWithTimeout方法通过添加超时阈值来扩展FindFirst,此值指定当元素不能立即可用时,脚本可以重试获取该元素的时间。
  • 该类Mouse公开了允许模拟鼠标操作的方法,安装“Microsoft.TestApi” NuGet 包后,此类即可使用,也可以使用其他方式来模拟单击和指针移动。
  • 模式方法(TablePattern.GetColumnHeaders()、ValuePattern.SetValue()等)允许您快速找到所需的元素、设置新的控件值、执行默认控件操作(例如单击)等等,正如在Appium和UI自动化一节中提到的,这些方法在Appium中不可用。
  • 要获得上下文菜单项,可以使用RootElements和TreeScope.Descendants。

C#:

AutomationElement menuItem = AutomationElement.RootElement.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.NameProperty, "itemName"));
((InvokePattern)menuItem.GetCurrentPattern(InvokePattern.Pattern)).Invoke();

VB.NET:

Dim globalDriverOptions As AppiumOptions = New AppiumOptions()
globalDriverOptions.AddAdditionalCapability("app", "Root")
Dim globalDriver = New WindowsDriver(Of WindowsElement)(New Uri("//127.0.0.1:4723"), globalDriverOptions)
Dim menuItem = globalDriver.FindElementByName("ItemName")
扫码咨询


添加微信 立即咨询

电话咨询

客服热线
023-68661681

TOP