移动应用中的 UI 测试:它是什么、类型以及如何进行

作者: IT Sectr 发布日期: 2026-04-07 阅读时间: 8 分钟

UI 测试检查移动应用用户界面元素——按钮、文本字段、列表和导航组件——的正确显示和交互。与检查业务逻辑的单元测试不同,UI 测试模拟用户操作:触摸、滑动、输入文本,并检查界面的响应。根据 Android Developers, 2024 的研究,UI 测试 覆盖了70%的关键用户场景,并能发现逻辑检查无法检测的布局缺陷。

主要要点

  • UI 测试 — 通过模拟用户操作(触摸、文本输入、滑动)来检查应用用户界面的过程。
  • Espresso — Google 提供的 Android 应用 UI 测试框架,支持与 UI 线程同步并自动等待动画结束。
  • XCUITest — Apple 的 iOS 应用 UI 测试原生框架,集成在 Xcode 中,通过 Accessibility 标签工作。
  • Appium — 跨平台工具,允许使用一种语言通过 WebDriver 协议为 Android 和 iOS 编写 UI 测试。
  • 快照测试 通过检查屏幕外观来补充 UI 测试——将参考状态的截图与当前渲染进行比较。

什么是 UI 测试?

UI 测试 是一种自动化检查类型,测试代码与应用的图形界面交互的方式与真实用户完全相同。测试在屏幕上找到元素——按钮、文本字段、列表——对其执行操作并检查界面的预期响应。例如,输入错误密码后,UI 测试会检查是否在屏幕上显示了含有正确文本的错误提示。

UI 测试与其他类型自动化的主要区别在于,它们通过操作系统的 无障碍层 工作,而不是通过应用的内部 API。这意味着 UI 测试看到的界面与用户和屏幕读取系统看到的完全一致。因此,UI 测试不仅检查功能性,还检查元素的无障碍性——是否符合 WCAG 要求。

根据 JetBrains Developer Ecosystem 2023 的调查,58%的移动团队在其 CI/CD 流水线中使用 UI 测试。商业项目中 UI 测试的平均覆盖率为应用屏幕的30–40%。有 UI 测试的项目在应用商店中收到的与界面崩溃相关的负面评论减少25%。

UI 测试与单元测试的区别

UI 测试和单元测试之间的主要区别是抽象级别。单元测试 与单独的类和函数工作,与 Android 或 iOS 框架隔离。它们在 JVM 上(针对 Android)执行,无需启动模拟器,仅需毫秒级。UI 测试在真实设备或模拟器上运行,与系统服务交互,每个场景需要秒或分钟。

测试的目标也不同。UI 测试 检查端到端的用户场景——注册、下订单、搜索。单元测试覆盖业务逻辑:计算、验证、数据转换。UI 测试不检查税务计算的正确性——它检查是否在屏幕上显示了最终金额。计算本身由单元测试检查。

根据 Google Testing Blog (2020),项目中测试的最佳比例应遵循测试金字塔规则:70%单元测试、20%集成测试和10% UI 测试。破坏这一比例偏向 UI 测试会导致执行时间增加和测试套件易碎性,因为 UI 测试对屏幕布局变化非常敏感。

UI 测试框架

对于 Android,主导框架是 Espresso——嵌入 AndroidX Test 中的 Google 库。Espresso 自动与 UI 线程同步,在执行下一次检查前等待动画和后台任务完成。对于 Jetpack Compose,使用 Compose UI Test 扩展,它通过语义节点工作,而非传统的视图标识符。

对于 iOS,主要工具是 Xcode 组件中的 XCUITest。测试使用 Swift 编写,利用 Accessibility 标识符查找元素。XCUITest 支持通过 record 功能录制测试,并通过 xcodebuild 与 CI 系统集成。对于跨平台项目,使用基于 WebDriver 协议的 Appium,可以在 Android 和 iOS 上以最小代码修改运行相同的测试。

Espresso 和 Compose UI Test

Espresso 通过 onView 和资源 id 标识符与传统的 View 系统工作。Compose UI Test 使用语义层,使测试对视图层级结构的依赖更少。例如,在 Espresso 中查找按钮:onView(withId(R.id.submit)),在 Compose 中:onNodeWithTag(“submit”)。Compose 测试自动处理重组,不需要显式等待闲置状态。

iOS 的 XCUITest

XCUITest 使用 XCUIApplication 作为入口点。每个界面元素通过 Accessibility 属性查找:accessibilityIdentifier 用于程序访问,accessibilityLabel 用于 VoiceOver。框架支持通过 Xcode 中的 record 功能录制测试——开发者在模拟器上执行操作,Xcode 生成测试代码。已完成的测试通过 xcodebuild test 运行。

跨平台解决方案

Appium 基于 WebDriver 协议,支持任何语言:Java、Python、JavaScript。查找元素时使用 id、xpath、class name 和 accessibility id 策略。Appium 需要安装服务器并配置 Desired Capabilities——platformName、deviceName、appPackage。替代方案是 Maestro,它使用 YAML 场景,无需编译测试代码。

  • Espresso — onView(withId(R.id.button)).perform(click()).check(matches(isDisplayed()))
  • XCUITest — app.buttons[“loginButton”].tap(); XCTAssertTrue(app.staticTexts[“welcome”].exists)
  • Appium — driver.findElement(By.id(“com.example:id/button”)).click()
  • Detox — Wix 提供的 React Native 框架,与 JS 线程同步
  • Maestro — 使用 YAML 场景的现代工具,无需编写代码

UI 测试代码示例

让我们看看三个不同框架中相同场景——登录应用——的 UI 测试:Espresso 用于 Android、XCUITest 用于 iOS、Appium 用于跨平台方法。场景:输入用户名和密码,点击登录按钮,检查欢迎消息是否显示。

Android:Espresso

Espresso 中的测试使用 onView 通过标识符查找元素,使用 perform 执行操作。带 isDisplayed 匹配器的 check 方法确认元素在屏幕上可见。

kotlin
@RunWith(AndroidJUnit4::class)
class LoginUiTest {

    @Rule
    @JvmField
    val composeTestRule = createComposeRule()

    @Test
    fun login_withValidCredentials_showsWelcome() {
        composeTestRule
            .onNodeWithTag("emailField")
            .performTextInput("user@example.com")
        composeTestRule
            .onNodeWithTag("passwordField")
            .performTextInput("secret123")
        composeTestRule
            .onNodeWithTag("loginButton")
            .performClick()
        composeTestRule
            .onNodeWithText("欢迎,用户!")
            .assertIsDisplayed()
    }
}

iOS:XCUITest

XCUITest 使用 XCUIApplication 通过 Accessibility 标识符访问界面元素。tap() 和 exists 方法提供交互和检查。

swift
class LoginUITests: XCTestCase {
    let app = XCUIApplication()

    override func setUp() {
        continueAfterFailure = false
        app.launch()
    }

    func testLogin_withValidCredentials_showsWelcome() {
        app.textFields["emailField"].tap()
        app.textFields["emailField"].typeText("user@example.com")
        app.secureTextFields["passwordField"].tap()
        app.secureTextFields["passwordField"].typeText("密码123")
        app.buttons["loginButton"].tap()
        XCTAssertTrue(app.staticTexts["Welcome, User!"].exists)
    }
}

UI 测试最佳实践

第一个原则——使用 Accessibility 标识符 而非文本标签来查找元素。按钮文本可能在本地化时改变,而标识符保持稳定。在 Android 中这是 contentDescription 属性,在 iOS 中是 accessibilityIdentifier。这种方法使测试不依赖于界面语言,并在文案更改时减少维护成本。

避免使用 sleep() 和固定延迟——使用框架内置的等待机制。Espresso 自动等待动画和后台任务完成。XCUITest 提供带超时的 XCTAssertTrue。显式停顿会减慢测试并使其不稳定,尤其是在 CI 环境中的慢速设备上。

按重要性对测试进行分组:smoke 测试(3–5个关键场景)在每次提交时运行,完整的 UI 测试套件在发布前运行。根据 Google Testing Blog (2022),在 CI 中超过30分钟的 UI 测试会将执行频率降低40%,这降低了其作为早期回归检测工具的有效性。

UI 测试的局限性及如何解决

UI 测试有一系列局限性。对布局变化的敏感性:标识符、层级结构或元素类型的变更会破坏测试,即使功能未变。解决方案——使用 Page Object 模式,将元素选择器集中在单独的类中。布局变更时,只需修改一个 Page Object 文件,而非数十个测试。

执行时间:在真实设备或模拟器上运行比单元测试多花10–50倍。解决方案——通过 Firebase Test Lab 或 AWS Device Farm 在多个设备上并行运行 UI 测试。不稳定性(flakiness)——CI 执行的常见问题,由动画、网络延迟或模拟器状态引起。对付 flakiness 的方法包括自动重试失败的测试和分析每个测试场景的稳定性。

常见问题

一个屏幕需要多少 UI 测试?

对于普通屏幕,3–5个 UI 测试就够了:常规路径、错误验证、空状态、方向变更和 Accessibility 检查。复杂屏幕 多状态——订单表单、设置——可能需要10–15个测试才能完全覆盖关键场景。

可以为 Android 和 iOS 使用同一个框架吗?

可以,Appium 和 Maestro 允许在两个平台上运行相同的场景。但是,原生框架——Espresso 和 XCUITest——提供更好的稳定性、速度和对平台功能的访问,这些通过 WebDriver 代理无法实现。

如何在 Jetpack Compose 中测试 UI?

对于 Compose,使用带有语义匹配器的 Compose UI Test 库:onNodeWithText、onNodeWithTag、onNodeWithContentDescription。Compose 的语义层抽象了视图层级结构,使测试比传统的 Espresso for View 系统更不易碎。

是否需要在物理设备上测试 UI?

基础 UI 测试在 CI 中的模拟器上执行——这快速且便宜。发布前的最终验证建议在 物理设备 上通过 Firebase Test Lab 进行,以考虑真实硬件的特点:不同分辨率、操作系统版本和性能。

如何减少 UI 测试的执行时间?

使用多个设备并行执行,通过 Developer Options 关闭模拟器上的动画,构建 模块化测试架构,并在每次提交时运行 smoke 套件,完整的回归测试按计划或在发布前运行。

总结

  • UI 测试 通过模拟用户操作检查界面——触摸、文本输入、滑动。
  • Espresso 和 Compose UI Test — Android 的主要框架;XCUITest — iOS ;Appium — 跨平台项目。
  • 测试金字塔 推荐比例 70/20/10:单元、集成和 UI 测试。
  • Accessibility 标识符 使 UI 测试抵御本地化和布局变化。
  • Page Object 集中元素选择器,减少界面变更时的维护成本。
  • Smoke 测试(3–5个场景)在每次提交时运行,完整套件在发布前运行。
  • 并行执行 在模拟器上并关闭动画可减少 CI 中的 UI 测试执行时间。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读