Xamarin.UITest
重要
Visual Studio App Center 计划于 2025 年 3 月 31 日停用。 虽然可以继续使用 Visual Studio App Center,直到它完全停用,但你可以考虑迁移到几个建议的替代方法。
Xamarin.UITest 是一个 C# 测试框架,使用 NUnit 进行 iOS 和 Android 应用中的 UI 验收测试。 它与 Xamarin.iOS 和 Xamarin.Android 项目紧密集成,但也可用于本机 iOS 和 Android 项目。 Xamarin.UITest 是允许 NUnit 测试在 Android 和 iOS 设备上执行的 自动化库 。 测试与用户界面交互,就像用户一样:输入文本、点击按钮和手势(如轻扫)。
通常,每个 Xamarin.UITest 都编写为称为 [Test]
的方法。 包含测试的类称为 [TestFixture]
。 测试装置包含单个测试或一组测试。 固定装置还负责设置,使测试运行并在测试完成时完成清理。 每个测试都应遵循 Arrange-Act-Assert 模式:
- 排列:测试将设置条件并初始化内容,以便可以执行测试。
- 操作:测试将与应用程序交互、输入文本、按下按钮等。
- 断言:测试检查在 Act 步骤中运行的操作的结果,以确定正确性。 例如,应用程序可能会验证是否显示特定的错误消息。
开始使用 Xamarin.UITest 的最佳时机是在移动应用程序开发期间。 自动测试是在根据以下列表中所述的步骤开发的一项功能时编写的:
- 在 Android 或 iOS 应用程序中开发该功能。
- 编写测试并在本地运行以验证功能。
- 在 App Center 测试中创建新的测试运行,或使用现有的测试运行。
- 编译 IPA 或 APK,然后将其与测试一起上传到 App Center 测试。
- 修复 App Center 测试公开的任何问题或 bug。
- 通过转到应用程序的下一个功能来重复此过程。
对于不再处于活跃开发状态的现有应用程序,以追溯方式添加自动测试可能不经济高效。 相反,更好的方法是在修复 bug 时使用 Xamarin.UITest。 例如,假设某个应用程序没有自动测试,并且用户报告了 bug。 分配用于修复该 bug 的开发人员可能需要执行一些 (或全部) 以下操作:
- 手动验证 bug 或回归。
- 使用演示 bug 的 Xamarin.UITest 编写测试。
- 将测试提交到 App Center 测试,以深入了解 Bug 的范围和对相关设备的影响。
- 修复 bug。
- 使用传递的 Xamarin.UITest 证明 bug 已修复。
- 提交修补程序并测试到 App Center 测试,以验证是否已在相关设备上修复该 bug。
- 将传递的测试检查到版本控制中。
自动化 UI 测试在很大程度上依赖于查找屏幕上的视图并与之交互。 Xamarin.UITest 通过两组相互协作的重要 API 满足此要求:
- 可在视图上执行的操作 - Xamarin.UITest 提供了允许测试模拟常见用户操作(如点击视图、输入文本或轻扫视图)的 API。
- 用于在屏幕上查找视图的查询 - Xamarin.UITest 框架的一部分是将在屏幕上查找视图的 API。 查询通过检查视图的属性并返回操作可能处理的对象,在运行时查找视图。 以这种方式查询是一种强大的技术,它允许为用户界面编写测试,无论屏幕大小、方向或布局如何
为了帮助编写测试,Xamarin.UITest 提供了 read-eval-print-loop (REPL) 。 REPL 允许开发人员和测试人员在应用程序运行时与屏幕交互,并简化了查询的创建。
Xamarin.UITest API 简介
与移动应用程序的所有测试交互都通过 实例 Xamarin.UITest.IApp
进行。 此接口定义了测试与应用程序协作以及与用户界面交互的关键方法。 此接口有两个具体实现:
-
Xamarin.UITest.iOS.iOSApp
此类将针对 iOS 自动执行测试。 -
Xamarin.UITest.Android.AndroidApp
此类适用于在 Android 上自动执行测试。
iOSApp
和 AndroidApp
对象不会直接实例化。 而是使用帮助程序 ConfigureApp
类创建它们。 此类是一个生成器,可确保 iOSApp
正确实例化 或 AndroidApp
。
建议对每个测试使用新 IApp
实例。 新实例可防止状态从一个测试溢出到另一个测试。 NUnit 测试可在两个位置初始化 实例 IApp
:
-
SetUp
在 方法中,测试固定装置是相关测试的逻辑分组,每个测试都独立于另一个测试运行。 在这种情况下,IApp
应在 方法中SetUp
初始化 ,确保每个测试都有新的IApp
可用。 -
TestFixtureSetup
在 方法中,在某些情况下,单个测试可能需要其自己的测试固定装置。 在这种情况下,在 方法中TestFixtureSetup
初始化IApp
对象一次可能更有意义。
配置后 IApp
,测试可能会开始与所测试的应用程序交互。 为此,需要获取对屏幕上可见的视图的引用。 Xamarin.UITest 中的许多方法都采用参数 Func<AppQuery, AppQuery>
来查找视图。 例如,以下代码片段演示如何点击按钮:
app.Tap(c=>c.Button("ValidateButton"));
Xamarin.UITest 框架中有两个接口实现 IApp
,一个用于 iOS,一个用于 Android。
初始化 iOS 应用程序的 IApp
当 Xamarin.UITest 在 iOS 上运行测试时,它会启动 iOS 模拟器的实例、部署应用程序、启动应用程序并开始运行测试。 iOS 应用程序必须已生成。 Xamarin.UITest 不会编译应用程序,也不会为你创建应用捆绑包。
方法 AppBundle
可用于指定可在文件系统上找到应用捆绑包的位置。 可通过两种方法实现此目的:绝对路径或相对路径。 此代码片段演示如何使用应用捆绑包的绝对路径:
IApp app = ConfigureApp
.iOS
.AppBundle("/path/to/iosapp.app")
.StartApp();
分部路径必须相对于 Xamarin.UITest 程序集。 此代码片段是一个示例:
IApp app = ConfigureApp
.iOS
.AppBundle("../../../iOSAppProject/bin/iPhoneSimulator/Debug/iosapp.app")
.StartApp();
相对路径示例指示 AppBundle
从 Xamarin.UITest 程序集上调三个目录,然后向下导航 iOS 应用程序项目的项目树以查找应用捆绑包。
ConfigureApp
确实具有其他方法来帮助配置 IApp
。 有关更多详细信息,请参阅 iOSAppConfigurator 类。 下表介绍了一些更有趣的方法:
方法 | 说明 |
---|---|
AppBundle |
此方法指定测试时要使用的应用捆绑包的路径。 |
Debug |
此方法将在测试运行器中启用调试日志记录消息。 此方法可用于排查在模拟器上运行应用程序时出现的问题。 |
DeviceIdentifier |
将设备配置为与设备标识符一起使用。 下面将更详细地介绍此方法。 |
EnableLocalScreenshots |
在本地运行测试时启用屏幕截图。 当测试在云中运行时,始终启用屏幕截图。 |
有关如何在特定 iOS 模拟器上运行 iOS 测试的详细信息,请参阅 确定 iOS 模拟器的设备 ID。
初始化适用于 Android 应用程序的 IApp
Xamarin.UITest 会将现有 APK 部署到已运行的附加设备或 Android 模拟器的实例。 应用将启动,然后运行测试。 Xamarin.UITest 无法生成 APK,也无法启动 Android 模拟器的实例。
ApkFile
的 方法IApp
用于指定在文件系统上可以找到 APK 的位置。 可通过两种方法实现此目的:绝对路径或相对路径。 此代码片段显示使用 APK 的绝对路径:
IApp app = ConfigureApp
.Android
.ApkFile("/path/to/android.apk")
.StartApp();
分部路径必须相对于 Xamarin.UITest 程序集。 此代码片段是一个示例:
IApp app = ConfigureApp
.Android
.ApkFile("../../../AndroidProject/bin/Debug/android.apk")
.StartApp();
相对路径示例指示 ApkFile
从 Xamarin.UITest 程序集中向上浏览三个目录,然后向下导航 Android 应用程序项目的项目树以查找 apk 文件。
如果连接了多个设备或仿真器,Xamarin.UITest 将停止测试执行并显示错误消息,因为它无法解析测试的预期目标。 在这种情况下,需要提供设备或仿真器的 串行 ID 才能运行测试。 例如,考虑命令的以下输出 adb devices
,其中列出了) 连接到计算机的所有设备 (或仿真器 (及其串行 ID) :
$ adb devices
List of devices attached
192.168.56.101:5555 device
03f80ddae07844d3 device
可以使用 方法指定 DeviceSerial
设备:
IApp app = ConfigureApp.Android.ApkFile("/path/to/android.apk")
.DeviceSerial("03f80ddae07844d3")
.StartApp();
与用户界面交互
为了与视图交互,许多 IApp
方法采用 Func<AppQuery, AppQuery>
委托来查找视图。 此委托使用 AppQuery
Xamarin.UITest 查找视图的方式的核心。
AppQuery
是用于生成查询以查找视图的 流畅接口 。 在提供 的方法中 AppQuery
, Marked
方法是最简单、最灵活的方法之一。 此方法使用启发式方法来尝试查找视图,将在下一部分中更详细地讨论。 目前,请务必了解 具有 IApp
许多与应用程序交互的方法。 这些方法使用 Func<AppQuery, AppQuery>
获取对要与之交互的视图的引用。 下面列出了 提供的 AppQuery
一些更有趣的方法:
方法 | 说明 |
---|---|
Button |
将在屏幕上找到一个或多个按钮。 |
Class |
将尝试查找属于指定类的视图。 |
Id |
将尝试查找具有指定 ID 的视图。 |
Index . |
将从匹配的视图集合中返回一个视图。 通常与其他方法结合使用。 采用从零开始的索引。 |
Marked |
将根据下面讨论的启发法返回视图。 |
Text |
将匹配包含所提供文本的视图。 |
TextField |
将匹配 Android EditText 或 iOS UITextField 。 |
例如,以下方法演示如何模拟点击名为“SaveUserdataButton”的按钮:
app.Tap(c=>c.Marked("SaveUserDataButton"));
由于 AppQuery
是一个 Fluent 接口,因此可以将多个方法调用链接在一起。 请考虑点击视图的这个更复杂的示例:
app.Tap(c=>c.Marked("Pending")
.Parent()
.Class("AppointmentListCell").Index(0));
在这里, AppQuery
将首先查找标记为 Pending
的视图,然后选择该视图的第一个 AppointmentListCell
父级,该视图的类型为 。
尝试通过查看移动应用来创建这些查询可能很棘手。 Xamarin.UITest 提供了一个 REPL,可用于浏览屏幕的视图层次结构、尝试创建查询,以及使用它们与应用程序交互。
使用 REPL
启动 REPL 的唯一方法是在现有测试中调用 IApp.Repl
方法。 这需要创建 NUnit TestFixture
,配置可在 方法中使用的 Test
实例IApp
。 以下代码片段演示了如何执行此操作的示例:
[TestFixture]
public class ValidateCreditCard
{
IApp app;
[SetUp]
public void Setup()
{
app = ConfigureApp.Android.ApkFile("/path/to/application.apk").StartApp();
}
[Test]
public void CreditCardNumber_TooLong_DisplayErrorMessage()
{
app.Repl();
}
}
若要运行测试,请右键单击 Visual Studio 的装订线并选择“ 运行”:
测试将运行,调用 方法时 Repl
,Xamarin.UITest 将在终端会话中启动 REPL,如以下屏幕截图所示:
REPL 已初始化名为 app
的 实例,该实例IApp
与应用程序交互。 首先要做的一件事是浏览用户界面。 REPL 有一个 tree
命令来执行此操作。 它将在显示的屏幕中打印出视图的层次结构。 例如,请考虑以下应用程序的屏幕截图:
可以使用 tree
命令显示此屏幕的以下层次结构:
App has been initialized to the 'app' variable.
Exit REPL with ctrl-c or see help for more commands.
>>> tree
[UIWindow > UILayoutContainerView]
[UINavigationTransitionView > ... > UIView]
[UITextView] id: "CreditCardTextField"
[_UITextContainerView]
[UIButton] id: "ValidateButton"
[UIButtonLabel] text: "Validate Credit Card"
[UILabel] id: "ErrorrMessagesTestField"
[UINavigationBar] id: "Credit Card Validation"
[_UINavigationBarBackground]
[_UIBackdropView > _UIBackdropEffectView]
[UIImageView]
[UINavigationItemView]
[UILabel] text: "Credit Card Validation"
>>>
我们可以看到,此视图中有一个 UIButton
具有 id
ValidateButton 的 。 可以使用 命令显示 tree
的信息来帮助创建必要的查询来查找视图并与之交互。 例如,以下代码模拟点击按钮:
app.Tap(c=>c.Marked("ValidateButton"))
输入命令时,REPL 会记住缓冲区中的命令。 REPL 提供一个 copy
命令,用于将此缓冲区的内容复制到剪贴板。 这使我们能够创建测试的原型。 可以使用 将 REPL 中完成的工作复制到剪贴板 copy
,然后将这些命令粘贴到 中 [Test]
。
使用标记查找视图
AppQuery.Marked 方法是一种方便而强大的方法来查询屏幕上的视图。 它的工作原理是检查屏幕上视图的视图层次结构,尝试将视图上的属性与提供的字符串匹配。
Marked
工作原理因操作系统而异。
查找已标记的 iOS 视图
iOS 视图将使用以下属性之一进行定位:
-
AccessibilityIdentifier
视图的 -
AccessibilityLabel
视图的
例如,请考虑以下 C# 代码片段,该代码片段创建 UILabel
并设置 AccessibilityLabel
:
UILabel errorMessagesTextField = new UILabel(new RectangleF(10, 210, 300, 40));
errorMessagesTextField.AccessibilityLabel = "ErrorMessagesTextField";
errorMessagesTextField.Text = String.Empty;
可以通过以下查询找到此视图:
AppResult[] results = app.Marked("ErrorMessagesTextField");
查找已标记的 Android 视图
Android 视图将基于以下属性之一进行定位:
-
Id
视图的 -
ContentDescription
视图的 -
Text
视图的
例如,假设 Android 布局定义了以下按钮:
<Button
android:text="Action 1"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/action1_button"
android:layout_weight="1"
android:layout_marginLeft="5dp" />
我们可以看到 android:id
,此按钮的 action1_button 为 android:text
操作 1。 以下两个查询之一将在屏幕上找到按钮:
app.Query(c=>c.Marked("action1_button"));
app.Query(c=>c.Marked("Action 1"));
使用 Xamarin.UITest.IApp 控制应用程序
配置并初始化后 IApp
,测试可能会开始与应用程序交互。 使用 Func<AppQuery, AppQuery>
的方法的一个示例是 IApp.Query()
方法。 此方法将执行查询并返回结果。 最简单的示例显示在以下代码片段中,该代码片段返回屏幕上可见的所有视图的列表:
AppResult[] results = app.Query(c=>c.All())
下表演示了使用 AppQuery
查找屏幕上视图的一些其他示例:
语法 | 结果 |
---|---|
app.Query(c=>c.Class("UILabel")) |
方法 .Class() 将查询作为 iOS UILabel 的子类的视图。 |
app.Query(c=>c.Id("txtUserName")) |
方法 .Id() 将查询具有 Id txtUserName 的 的视图。 |
app.Query(c=>c.Class("UILabel").Text("Hello, World")) |
查找文本为“Hello, World”的所有 UILabel 类。 |
results = app.Query(c=>c.Marked("ValidateButton")) |
返回用指定文本 标记 的所有视图。 方法是 Marked 一个有用的方法,可以简化查询。 下一部分将介绍此内容。 |
下表列出了一些 (但并非所有) ,这些方法 IApp
可用于与屏幕上的视图交互或操作:
示例 | 说明 |
---|---|
PressEnter |
在应用中按 Enter 键。 |
Tap |
模拟匹配元素上的点击/触摸手势。 |
EnterText |
在视图中输入文本。 在 iOS 应用程序中,Xamarin.UITest 将使用软键盘输入文本。 相比之下,Xamarin.UITest 不会使用 Android 键盘,它会直接将文本输入到视图中。 |
WaitForElement |
暂停测试的执行,直到视图显示在屏幕上。 |
Screenshot(String) |
获取处于当前状态的应用程序的屏幕截图,并将其保存到磁盘。 它返回一个 对象, FileInfo 其中包含有关所拍摄屏幕截图的信息。 |
Flash |
此方法将导致所选视图在屏幕上“闪烁”或“闪烁”。 |
有关 接口的详细信息IApp
,请参阅 、 AndroidApp
和 iOSApp
的 IApp
API 文档。
作为如何使用这些方法的示例,请考虑对上面显示的屏幕截图进行以下测试。 此测试将在文本字段中输入 17 位数的信用额度卡,然后点击屏幕上的按钮。 然后,它会检查屏幕中是否有错误消息,告知用户该号码太长,无法成为有效的信用额度卡号码:
[Test]
public void CreditCardNumber_TooLong_DisplayErrorMessage()
{
/* Arrange - set up our queries for the views */
// Nothing to do here, app has been instantiated in the [SetUp] method.
/* Act */
app.EnterText(c => c.Marked("CreditCardTextField"), new string('9', 17));
// Screenshot can be used to break this test up into "steps".
// The screenshot can be inspected after the test run to verify
// the visual correctness of the screen.
app.Screenshot("Entering a 17 digit credit card number.");
app.Tap(c => c.Marked("ValidateButton"));
app.Screenshot("The validation results.");
/* Assert */
AppResult[] result = app.Query(c => c.Class("UILabel").Text("Credit card number is too long."));
Assert.IsTrue(result.Any(), "The error message isn't being displayed.");
}
此测试还使用 Screenshot
方法在测试执行期间的关键点拍摄照片。 运行此测试时,App Center 将获取屏幕截图并将其显示在测试结果中。 方法允许将测试分解为步骤并提供屏幕截图的说明。