测试 Android 应用的基础

本页面概述了测试 Android 应用的核心原则,包括核心最佳实践及其好处。

测试的好处

测试是应用开发过程中的一个不可或缺的部分。通过持续对您的应用运行测试,您可以在公开发布之前验证应用的正确性、功能行为和可用性。

您可以通过手动测试您的应用来浏览它。您可能会使用不同的设备和模拟器,更改系统语言,并尝试生成每个用户错误或遍历每个用户流程。

但是,手动测试的扩展性很差,并且很容易忽略应用行为的回归。自动化测试涉及使用为您执行测试的工具,这更快、更可重复,并且通常在开发过程的早期为您提供更多关于应用的可操作反馈。

Android 中的测试类型

移动应用很复杂,必须在许多环境中正常运行。因此,有许多类型的测试。

主题

例如,根据主题,存在不同类型的测试

  • 功能测试:我的应用是否按预期运行?
  • 性能测试:它是否快速高效地运行?
  • 辅助功能测试:它是否与辅助功能服务配合良好?
  • 兼容性测试:它是否在每个设备和 API 级别上都能正常运行?

范围

测试也根据大小隔离程度而异

  • 单元测试小型测试仅验证应用的极小部分,例如方法或类。
  • 端到端测试或大型测试同时验证应用的更大部分,例如整个屏幕或用户流程。
  • 中等测试介于两者之间,并检查两个或多个单元之间的集成
Tests can be either small, medium, or big.
图 1:典型应用程序中的测试范围。

对测试进行分类的方法很多。然而,对于应用程序开发人员来说,最重要的区别是测试运行的位置。

仪器测试与本地测试

您可以在 Android 设备或其他计算机上运行测试

  • 仪器测试在 Android 设备上运行,无论是物理设备还是模拟器。该应用程序与测试应用程序一起构建和安装,该应用程序注入命令并读取状态。仪器测试通常是 UI 测试,启动应用程序,然后与之交互。
  • 本地测试在您的开发机器或服务器上执行,因此它们也被称为主机端测试。它们通常很小且速度快,将测试对象与应用程序的其余部分隔离开来。
Tests can run as instrumented tests on a device, or local tests on your development machine.
图 2:根据测试运行位置的不同类型的测试。

并非所有单元测试都是本地的,并非所有端到端测试都在设备上运行。例如

  • 大型本地测试:您可以使用在本地运行的 Android 模拟器,例如 Robolectric
  • 小型仪器测试:您可以验证您的代码是否与框架功能(例如 SQLite 数据库)良好配合。您可以在多个设备上运行此测试,以检查与多个 SQLite 版本的集成。

示例

以下代码段演示了如何在仪器 UI 测试中与 UI 交互,该测试单击一个元素并验证另一个元素是否显示。

Espresso

// When the Continue button is clicked
onView(withText("Continue"))
    .perform(click())

// Then the Welcome screen is displayed
onView(withText("Welcome"))
    .check(matches(isDisplayed()))

Compose UI

// When the Continue button is clicked
composeTestRule.onNodeWithText("Continue").performClick()

// Then the Welcome screen is displayed
composeTestRule.onNodeWithText("Welcome").assertIsDisplayed()

此代码段显示了单元测试的 ViewModel(本地,主机端测试)的一部分

// Given an instance of MyViewModel
val viewModel = MyViewModel(myFakeDataRepository)

// When data is loaded
viewModel.loadData()

// Then it should be exposing data
assertTrue(viewModel.data != null)

定义测试策略

在理想情况下,您将在应用程序兼容的每个设备上测试应用程序的每一行代码。不幸的是,这种方法速度太慢,成本太高,不切实际。

良好的测试策略在测试的保真度、速度和可靠性之间找到了合适的平衡。测试环境与真实设备的相似性决定了测试的保真度。保真度更高的测试在模拟设备或物理设备本身运行。保真度较低的测试可能在您的本地工作站的 JVM 上运行。高保真度测试通常速度较慢,需要更多资源,因此并非所有测试都应该是高保真度测试。

不稳定的测试

即使在设计和实施正确的测试运行中,也会发生错误。例如,当在真实设备上运行测试时,自动更新可能会在测试过程中启动,导致测试失败。您代码中的细微竞争条件可能只在很小一部分时间内发生。无法 100% 通过的测试是不稳定的。

可测试架构

使用可测试的应用程序架构,代码遵循一种结构,允许您轻松地隔离测试其不同部分。可测试架构具有其他优点,例如可读性、可维护性、可扩展性和可重用性更好。

不可测试的架构会产生以下结果

  • 更大、更慢、更不稳定的测试。无法进行单元测试的类可能需要通过更大的集成测试或 UI 测试来覆盖。
  • 测试不同场景的机会更少。更大的测试速度较慢,因此测试应用程序的所有可能状态可能不现实。

要了解有关架构指南的更多信息,请参阅 应用程序架构指南

解耦方法

如果您可以将函数、类或模块的一部分从其余部分中提取出来,那么测试它会更容易,更有效。这种做法被称为解耦,它是可测试架构中最重要的概念。

常见的解耦技术包括以下内容

  • 将应用程序拆分为,例如演示、域和数据。您还可以将应用程序拆分为模块,每个功能一个模块。
  • 避免在具有大型依赖项的实体(例如活动和片段)中添加逻辑。将这些类用作框架的入口点,并将UI 和业务逻辑移至其他地方,例如 Composable、ViewModel 或域层。
  • 在包含业务逻辑的类中避免直接框架依赖项。例如,不要在 ViewModel 中使用 Android 上下文
  • 使依赖项易于替换。例如,使用 接口 而不是具体实现。使用 依赖注入,即使您没有使用 DI 框架。

后续步骤

现在您已经了解了为什么要测试以及两种主要的测试类型,您可以阅读 测试什么

或者,如果您想创建第一个测试并通过实践学习,请查看 测试代码实验室