测试是App整体开发流程的中的重要一部分,通过运行测试,可以在发布应用之前充分验证应用的正确性,功能特性和可用性。
测试还有以下优点:
用户可以在各种层次上进行应用交互,从按下按钮到将信息下载到他们的设备上。因此,我们应该在迭代开发应用程序时测试各种用例和交互。
随着应用程序的扩展,可能会发现有必要从服务器获取数据,与设备的传感器交互,访问本地存储或呈现复杂的用户界面。应用程序的多功能性需要全面的测试策略。
在迭代开发功能时,可以先编写一个新测试,也可以将示例和断言添加到现有的单元测试中。测试刚开始会失败,因为该功能尚未实现。
在设计新功能时考虑出现的功能单元非常重要。对于每个单元,编写相应的单元测试。您的单元测试应该几乎覆盖与设备的所有可能的交互,包括标准交互,无效输入和资源不可用的情况。建议尽可能利用Jetpack库;当使用这些经过良好测试的库时,可以专注于验证应用的功能特性。
图1:以上两个循环与迭代,测试驱动的开发相关联
完整的工作流程,如图1所示,包含一系列嵌套的迭代循环,其中包含一个长而缓慢的UI驱动循环测试代码单元的集成。我们可以使用更短,更快的开发周期自行测试单元。这组循环测试一直持续到应用程序满足每个用例为止。
为了使代码更易于测试,建议根据组件化来开发代码,其中每个模块代表用户在应用程序中完成的特定任务。这个视角与应用程序的基于堆栈的视图形成对比,该应用通常包含表示UI层,业务逻辑层和数据层。
例如,“任务列表”应用程序可能具有用于创建任务,查看已完成任务的统计信息以及将照片与特定任务相关联的模块。这种模块化体系结构还可以帮助我们将不相关的类分离,并为开发团队分配所有权提供了一种自然的结构。
在每个模块周围设置明确定义的边界,并在应用程序规模和复杂性增长时创建新模块非常重要。每个模块应该只有一个关注区域,并且模块间通信的API应该是一致的。为了更轻松,更快速地测试这些模块间交互,建议创建模块的空实现。在测试中,一个模块的真实实现可以调用另一个模块的空实现。
然而,当我们创建一个新模块时,不要教条式地要求它马上具有完整的功能。对于一个特定的模块,没有一个或多个层级,是没有关系的。
要了解有关如何在应用程序中定义模块的更多信息,以及有关创建和发布模块的平台支持,请参阅Android App Bundles。
在配置环境和依赖关系以在应用中创建测试时,请遵循本节下面介绍的最佳方法。
Android Studio中的典型项目包含两个放置测试的目录。按如下方式组织测试:
在设备上运行测试时,您可以选择以下类型:
真实设备提供最高保真度,但也花费大量时间来运行测试。另一方面,模拟设备以较低保真度为代价提供改进的测试速度。然而,该平台在二进制资源和现实技术中的改进使得模拟设备能够产生更真实的结果。
虚拟设备提供了保真度和速度的平衡。使用虚拟设备进行测试时,使用快照可以最大程度地缩短测试之间的设置时间。
创建测试时,可以选择创建真实对象或测试副本,例如假对象或模拟对象。通常,在测试中使用真实对象比使用测试副本更好,尤其是当测试对象满足以下条件之一时:
但是,如果您的测试尝试在真实对象上执行以下类型的操作,则最好创建伪造或甚至模拟对象:
提示:请与库作者核实,看看他们是否提供了可以可靠依赖的任何官方支持的测试基础架构,例如仿造。
在配置测试环境之后,可以开始编写评估应用程序功能的测试了。本节介绍如何编写小型,中型和大型测试。
金字塔包含三层
图2.测试金字塔,显示应该包含在应用程序测试套件中的三类测试
测试金字塔,如图2所示,说明了您的应用程序应如何包含三类测试:小型,中型和大型:
当处理金字塔时,从小型测试到大型测试,每次测试都会提高保真度,但也会增加执行时间和维护和调试的工作量。因此,应该编写比集成测试更多的单元测试,以及比端到端测试更多的集成测试。虽然每个类别的测试比例可能因应用程序的使用情况而异,但我们通常建议在类别中进行以下划分:70%小测试,20%中等测试,10%大测试。
编写的小测试应该是高度集中的单元测试,可以详尽地验证应用程序中每个类的功能和约束。
在特定类中添加和更改方法时,建议针对它们创建并运行单元测试。如果这些测试依赖于Android框架,请使用统一的,与设备无关的API,例如androidx.test API。这样就可以在没有物理设备或模拟器的情况下在本地运行测试。
如果测试依赖于资源,请在应用程序的build.gradle文件中启用includeAndroidResources选项。然后,您的单元测试可以访问资源的编译版本,从而使测试更加快速准确地运行。
app/build.gradle
android {
// ...
testOptions {
unitTests {
includeAndroidResources = true
}
}
}
备注:Android Studio 3.4及更高版本默认提供资源的编译版本。
尽可能使用AndroidX Test API,以便单元测试可以在设备或模拟器上运行。对于始终在JVM驱动的开发机器上运行的测试,可以使用Robolectric。
Robolectric模拟Android 4.1(API级别16)或更高版本的运行时,并提供称为阴影的社区维护副本。此功能可以让测试只依赖于这个框架,而无需使用模拟器或模拟对象。 Robolectric支持Android平台的以下方面:
我们可以在物理设备或仿真器上运行已检测的单元测试。但是,这种形式的测试涉及的执行时间明显慢于本地单元测试,因此,只有在评估应用程序对实际设备硬件的行为至关重要时才最好使用此方法。
运行检测测试时,AndroidX Test使用以下线程:
如果需要在主线程上执行测试,请使用@UiThreadTest对其进行注释。
除了通过运行小测试来测试应用程序的每个单元之外,您还应该从模块级别验证应用程序的行为。为此,编写中等测试,这是集成测试,用于验证一组单元的协作和交互。
让应用程序结构和以下中等测试示例(按增加范围的顺序)来定义表示应用程序中单元组的最佳方式:
要执行这些测试,请执行以下操作:
使用Espresso Intents库中的方法。要简化传递给这些测试的信息,请使用副本和存根。
结合使用IntentSubject和基于Truth的断言来验证捕获的意图。
运行仪器介质测试时使用Espresso
当您在设备或Robolectric上执行类似于以下的UI交互时,Espresso可帮助保持任务同步:
要了解有关这些交互以及如何在应用测试中使用它们的详细信息,请参阅Espresso指南。
虽然单独测试应用程序中的每个类和模块非常重要,但验证引导用户完成多个模块和功能的端到端工作流同样重要。这些类型的测试在代码中形成了不可避免的瓶颈,但我们可以通过验证尽可能接近实际成品的应用程序来最小化此影响。
注意:对于编写的每个基于工作流的大型测试,您还应编写中等测试,以检查工作流中包含的每个模块的功能。该测试结构使得更容易确定关键用户旅程的哪个步骤表现出意外行为。
如果您的应用程序足够小,您可能只需要一套大型测试来评估整个应用程序的功能。否则,您应该按团队所有权,功能垂直或用户目标划分大型测试套件。
通常,最好在模拟设备或基于云的服务(如Firebase测试实验室)上测试您的应用,而不是在物理设备上,因为您可以更轻松,更快速地测试多种屏幕尺寸和硬件配置组合。
Espresso中的同步支持
除了支持中型仪器测试外,Espresso还在大型测试中完成以下任务时提供同步支持:
要了解有关这些交互以及如何在应用测试中使用它们的详细信息,请参阅Espresso指南。
本节介绍如何使用AndroidX Test的元素来进一步优化应用程序的测试。
Guava团队提供了一个名为Truth的流畅断言库。在构建验证步骤或测试步骤时,您可以使用此库作为基于JUnit或Hamcrest的断言的替代方法。
通常,您使用Truth使用包含您正在测试的条件的短语来表达特定对象具有特定属性,例如:
AndroidX Test支持Android的其他几个主题,使基于Truth的断言更容易构建:
AndroidX Test API可帮助您执行与移动应用程序测试相关的常见任务,以下各节将对此进行讨论。
Espresso可以以线程安全的方式以编程方式定位应用程序中的UI元素并与之交互。要了解更多信息,请参阅Espresso指南。
AndroidJUnitRunner类定义了一个基于检测的JUnit测试运行器,它允许在Android设备上运行JUnit 3或JUnit 4样式的测试类。测试运行器有助于将测试包和被测应用程序加载到设备或模拟器上,运行测试并报告结果。
要进一步提高这些测试的可靠性,请使用Android Test Orchestrator,它在自己的Instrumentation沙箱中运行每个UI测试。此体系结构可减少测试之间的共享状态,并在每个测试的基础上隔离应用程序崩溃。有关Android Test Orchestrator在测试应用时提供的好处的详细信息,请参阅Android Test Orchestrator指南。
UI Automator API允许您与设备上的可见元素进行交互,无论具有焦点的activity或fragment如何。
警告:仅建议在应用必须与系统UI或其他应用交互以满足关键用例时才使用UI Automator测试您的应用。由于UI Automator与特定系统UI交互,因此您必须在每个平台版本升级后以及每个新版Google Play服务之后重新运行并修复UI Automator测试。
作为使用UI Automator的替代方案,我们建议添加密封测试或将大型测试分成一套中小型测试。特别是,重点是一次测试一个应用程序间通信,例如将信息发送到其他应用程序并响应intent结果。 Espresso-Intents工具可以帮助您编写这些较小的测试。
添加辅助功能检查以验证一般可用性
应用程序界面应允许所有用户(包括具有辅助功能需求的用户)与设备进行交互,并在您的应用中更轻松地完成任务。
为了帮助验证应用程序的可访问性,Android的测试库提供了几个内置功能,将在以下各节中讨论。要详细了解如何针对不同类型的用户验证应用的可用性,请参阅有关测试应用可访问性的指南。
Robolectric
通过在测试套件的开头包含@AccessibilityChecks注释来启用可访问性检查,如以下代码段所示:
Kotlin
import org.robolectric.annotation.AccessibilityChecks
@AccessibilityChecks
class MyTestSuite {
// Your tests here.
}
Java
import org.robolectric.annotation.AccessibilityChecks;
@AccessibilityChecks
public class MyTestSuite {
// Your tests here.
}
Espresso
通过在测试代码的setUp()方法中调用AccessibilityChecks.enable()来启用可访问性检查,如以下代码段所示。
有关如何解释这些辅助功能检查结果的详细信息,请参阅Espresso辅助功能检查指南。
Kotlin
import androidx.test.espresso.accessibility.AccessibilityChecks
@Before
fun setUp() {
AccessibilityChecks.enable()
}
Java
import androidx.test.espresso.accessibility.AccessibilityChecks
@Before
fun setUp() {
AccessibilityChecks.enable()
}
使用ActivityScenario和FragmentScenario类来测试应用程序的activity和fragment如何响应系统级中断和配置更改。要了解更多信息,请参阅有关如何测试activity和fragment片段的指南。
注意:考虑到fragment表现的特定于设备的条件,最好将FragmentScenario作为仪器化测试的一部分。
AndroidX Test包含用于管理关键服务生命周期的代码。要了解如何定义这些规则,请参阅JUnit4规则指南。
如果您的应用的功能取决于设备的SDK版本,请使用@SdkSuppress注释,传入minSdkVersion或maxSdkVersion的值,具体取决于您如何分支应用的逻辑:
Kotlin:
@Test
@SdkSuppress(maxSdkVersion = 27)
fun testButtonClickOnOreoAndLower() {
// ...
}
@Test
@SdkSuppress(minSdkVersion = 28)
fun testButtonClickOnPieAndHigher() {
// ...
}
Java:
@Test
@SdkSuppress(maxSdkVersion = 27)
public void testButtonClickOnOreoAndLower() {
// ...
}
@Test
@SdkSuppress(minSdkVersion = 28)
public void testButtonClickOnPieAndHigher() {
// ...
}
有关在Android上进行测试的更多信息,请参阅以下资源。
例子
Android测试例子
Codelabs
Android 测试 Codelab