Xamarin.UITest

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 模式:

  1. 排列:测试将设置条件并初始化内容,以便可以操作测试。
  2. 操作:测试将与应用程序交互、输入文本、推送按钮等。
  3. 断言:测试检查"操作"步骤中运行的操作的结果,以确定正确性。 例如,应用程序可能会验证是否显示特定错误消息。

开始使用 Xamarin.UITest 的最佳时间是在开发移动应用程序期间。 自动测试是按照以下列表中所述的步骤开发的一项功能编写的:

  1. 在 Android 或 iOS 应用程序中开发该功能。
  2. 编写测试并在本地运行以验证功能。
  3. 在 App Center 中创建新的测试运行,或使用现有的测试运行。
  4. 编译 IPA 或 APK,然后将其连同测试一起上传到 App Center 测试。
  5. 修复 App Center 测试公开的任何问题或 bug。
  6. 通过移动到应用程序的下一项功能,重复该过程。

对于不再处于主动开发环境中的现有应用程序,以追溯方式添加自动测试可能不会经济高效。 相反,更好的方法是在修复 bug 时使用 UITest。 例如,假设某个应用程序没有自动测试,并且用户报告了 bug。 为修复 bug 而分配的开发人员可能需要执行以下 (或全部) 操作:

  • 手动验证 bug 或回归。
  • 使用阐释 bug 的 UITest 编写测试。
  • 将测试提交到 App Center 测试,以深入了解相关设备上 bug 的范围和影响。
  • 修复 bug。
  • 证明 bug 已通过传递 UITest 进行修复。
  • 将修补程序和测试提交到 App Center 测试,以验证是否已在相关设备上修复了 bug。
  • 检查将测试传递到版本控制中。

自动 UI 测试很大程度上依赖于在屏幕上查找和与视图进行交互。 UITest 使用两个重要的 Api 集来解决此需求:

  1. 可对视图执行的 操作-Xamarin. UITest 提供了 api,使测试可以模拟常见用户操作,例如点击视图、输入文本或在视图上轻扫。
  2. 用于在屏幕上查找视图的 查询-UITest 框架的一部分是用于在屏幕上查找视图的 api。 查询通过检查视图的特性并返回操作可能适用的对象,在运行时找到视图。 以这种方式进行查询是一种功能强大的技术,它允许针对屏幕大小、方向或布局的用户界面编写测试

为了帮助编写测试,UITest 提供了 (复制) 的 读取 eval 打印循环。 使用复制功能,开发人员和测试人员可以在应用程序运行时与屏幕交互,并简化创建查询。

介绍 UITest API

与移动应用程序的所有测试交互都通过实例进行 Xamarin.UITest.IApp 。 此接口定义对与应用程序进行协作并与用户界面交互的测试的关键方法。 此接口有两种具体的实现方式:

  • Xamarin.UITest.iOS.iOSApp 此类将对 iOS 自动执行测试。
  • Xamarin.UITest.Android.AndroidApp 此类用于在 Android 上自动执行测试。

iOSAppAndroidApp 对象不是直接实例化的。 相反,它们是使用帮助器类创建的 ConfigureApp 。 此类是一个生成器,可确保 iOSAppAndroidApp 已正确实例化。

建议 IApp 为每个测试使用一个新的实例。 新实例会阻止一个测试溢出中的状态转换为另一个测试。 NUnit 测试可以在两个位置初始化实例 IApp

  • 通常,SetUp 方法中,测试装置是相关测试的逻辑分组,其中每个测试装置独立运行。 在此方案中, IApp 应在方法中进行初始化 SetUp ,以确保 IApp 每个测试都有新的可用。
  • 在某些情况下 ,该 TestFixtureSetup 方法 中的单个测试可能需要自己的测试装置。 在这种情况下, IApp 在方法中多次初始化对象可能更有意义 TestFixtureSetup

配置完成后 IApp ,测试可能开始与正在测试的应用程序交互。 为此,需要获取对屏幕上可见的视图的引用。 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();

相对路径示例指示从 Xamarin.UITest 程序集向上浏览三个目录,然后导航到 iOS 应用程序项目的项目 AppBundle 树以查找应用捆绑包。

ConfigureApp 还有其他方法可帮助配置 IApp 。 有关更多详细信息 ,请参阅 iOSAppConfigurator 类。 下表描述了一些更有趣的方法:

方法 说明
AppBundle 此方法指定测试时要使用的应用捆绑的路径。
Debug 此方法将在测试运行程序中启用调试日志记录消息。 此方法可用于排查在模拟器上运行应用程序时出现问题的问题。
DeviceIdentifier 将设备配置为与设备标识符一起使用。 下面将更详细地介绍此方法。
EnableLocalScreenshots 在本地运行测试时启用屏幕截图。 当测试在云中运行时,始终启用屏幕截图。

若要详细了解如何在特定的 iOS 模拟器上运行 iOS 测试,请参阅确定 iOS 模拟器的设备 ID。

初始化适用于 Android 应用程序的 IApp

Xamarin.UITest 将现有 APK 部署到已连接的设备或已运行的 Android 仿真器的实例。 将启动应用,然后运行测试。 Xamarin.UITest 无法生成 APK,也无法启动 Android 仿真器的实例。

ApkFileIApp 方法用于指定可以在文件系统上找到 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();

相对路径示例指示从 Xamarin.UITest 程序集向上浏览三个目录,然后向下导航到 Android 应用程序项目的项目树以 ApkFile 查找 apk 文件。

如果连接了多个设备或模拟器,Xamarin.UITest 将停止测试执行并显示错误消息,因为它无法解析测试的目标。 在这种情况下,必须提供设备或模拟器的串行 ID 来运行测试。 例如,请考虑命令中的以下输出,其中列出了连接到计算机 (的所有设备) 及其串行 ID (仿真 adb devices 器) :

$ 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 是一个流畅的接口,因此可以将多个方法调用链接在一起。 请考虑这个更复杂的视图示例:

app.Tap(c=>c.Marked("Pending")
            .Parent()
            .Class("AppointmentListCell").Index(0));

此时, AppQuery 将首先查找标记为的视图 Pending ,然后选择该视图中类型为的第一个父项 AppointmentListCell

尝试通过查看移动应用来创建这些查询可能会很棘手。 Xamarin.UITest 提供了一个 REPL,可用于浏览屏幕的视图层次结构、尝试创建查询,并使用它们与应用程序交互。

使用 REPL

启动 REPL 的唯一方法是调用 IApp.Repl 现有测试内的 方法。 这需要创建一个 NUnit,配置可在 方法 TestFixture IApp 中使用的 Test 实例。 以下代码片段演示了如何这样做的示例:

[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,如以下屏幕截图所示:

运行 Xamarin.UITest REPL 的 macOS 终端的屏幕截图

REPL 已初始化名为 的 实例 IApp app ,该实例与应用程序交互。 要执行的第一件事之一是浏览用户界面。 REPL 具有 tree 用于执行此操作的命令。 它将在显示的屏幕中打印视图的层次结构。 例如,请考虑应用程序的以下屏幕截图:

iPhone 上运行的示例应用程序的屏幕截图

可以使用 命令 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&quot;
      [_UITextContainerView]
    [UIButton] id: &quot;ValidateButton&quot;
      [UIButtonLabel] text: &quot;Validate Credit Card&quot;
    [UILabel] id: &quot;ErrorrMessagesTestField&quot;
  [UINavigationBar] id: &quot;Credit Card Validation&quot;
    [_UINavigationBarBackground]
      [_UIBackdropView > _UIBackdropEffectView]
      [UIImageView]
    [UINavigationItemView]
      [UILabel] text: &quot;Credit Card Validation&quot;
>>>

可以看到,此视图中有一个 ,其 为 UIButton id ValidateButton。 我们可以使用 命令显示的信息来帮助创建必要的查询来 tree 查找视图并与之交互。 例如,以下代码模拟点击按钮:

app.Tap(c=>c.Marked(&quot;ValidateButton"))

在输入命令时,REPL 会记住缓冲区中的命令。 复制提供了一个 copy 命令,该命令会将此缓冲区的内容复制到剪贴板。 这使我们可以对测试进行原型。 我们可以将在复制中完成的工作复制到剪贴板 copy ,然后将这些命令粘贴到中 [Test]

使用标记的查找视图

AppQuery方法是在屏幕上查询视图的一种便捷而有效的方法。 它的工作原理是检查屏幕上视图的视图层次结构,尝试将视图上的属性与提供的字符串进行匹配。 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()方法将使用 txtUserName 的来查询视图 Id
app.Query(c=>c.Class("UILabel").Text("Hello, World")) 查找所有 UILabel 文本为 "Hello,World" 的类。
results = app.Query(c=>c.Marked("ValidateButton")) 返回用指定文本 标记 的所有视图。 Marked方法是一个可以简化查询的有用方法。 下面的部分将对此进行介绍。

下表列出了一些 (但不是所提供的方法的所有) IApp ,可用于与屏幕上的视图进行交互或操作:

示例 说明
PressEnter 在应用中按 enter 键。
Tap 模拟匹配元素上的点击/触摸手势。
EnterText 在视图中输入文本。 在 iOS 应用程序中,UITest 将使用软键盘输入文本。 与此相反,UITest 不会使用 Android 键盘,它会直接在视图中输入文本。
WaitForElement 暂停执行测试,直到视图显示在屏幕上。
Screenshot(String) 拍摄当前状态的应用程序的屏幕截图,并保存到磁盘。 它返回 FileInfo 一个 对象,该对象包含有关所拍摄屏幕截图的信息。
Flash 此方法将导致所选视图在屏幕上"闪烁"或"闪烁"。

有关 接口详细信息,请参阅 、 和 的 IApp API IApp AndroidApp 文档 iOSApp

作为如何使用这些方法的示例,请考虑对上面显示的屏幕截图进行以下测试。 此测试将在文本字段中输入信用卡的 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屏幕截图,并显示在测试结果中。 方法允许将测试分解为步骤,并提供屏幕截图的说明。