测试应用

Android Studio 的宗旨就是要简化测试。您只需点击几下,便可建立在本地 JVM 上运行的 JUnit 测试,或建立在设备上运行的插桩测试。当然,您也可以通过集成测试框架来扩展测试能力,例如可以集成 Mockito 在本地单元测试中测试 Android API 调用,以及集成 EspressoUI Automator 在插桩测试中测试用户交互。您可以使用 Espresso 测试记录器自动生成 Espresso 测试。

本页提供了一些基本信息,介绍了如何向您的应用添加新测试并从 Android Studio 运行这些测试。

如需查看更完整的测试编写方法指南,请参阅在 Android 平台上测试应用

测试类型和位置

测试代码的位置取决于您要编写的测试的类型。Android Studio 为以下两种类型的测试提供了源代码目录(源集):

本地单元测试

位于 module-name/src/test/java/

这些测试在计算机的本地 Java 虚拟机 (JVM) 上运行。当您的测试没有 Android 框架依赖项或当您可以模拟 Android 框架依赖项时,可以使用这些测试来最大限度地缩短执行时间。

在运行时,这些测试的执行对象是去掉了所有 final 修饰符的修改版 android.jar。这样一来,您就可以使用 Mockito 之类的常见模拟库。

插桩测试

位于 module-name/src/androidTest/java/

这些测试在硬件设备或模拟器上运行。这些测试有权访问 Instrumentation API,让您可以获取某些信息(例如您要测试的应用的 Context),并且允许您通过测试代码来控制受测应用。在编写集成和功能界面测试来自动执行用户交互时,或者当您的测试具有模拟对象无法满足的 Android 依赖项时,可以使用这些测试。

由于插桩测试内置于 APK 中(与您的应用 APK 分离),因此它们必须拥有自己的 AndroidManifest.xml 文件。不过,由于 Gradle 在编译期间自动生成此文件,因此它不会显示在您的项目源集中。您可以在必要时(例如需要为 `minSdkVersion` 指定其他值或注册测试专用的运行监听器时)添加自己的清单文件。编译您的应用时,Gradle 会将多个清单文件合并成一个清单。

Gradle 编译系统解读这些测试源集的方式与其解读项目应用源集的方式相同,您可以利用这一点根据编译变体创建测试

当您创建新项目或添加应用模块时,Android Studio 会创建上面列出的测试源集,并在每个源集中加入一个示例测试文件。您可以在 Project 窗口中看到它们,如图 1 所示。

图 1. 您的项目的 (1) 插桩测试和 (2) 本地 JVM 测试显示在 Project 视图(左侧)或 Android 视图(右侧)中。

添加新测试

要创建本地单元测试或插桩测试,您可以按照以下步骤针对特定类或方法创建新测试:

  1. 打开包含要测试的代码的 Java 文件。
  2. 点击要测试的类或方法,然后按 Ctrl+Shift+T 键 (⇧⌘T)。
  3. 在显示的菜单中,点击 Create New Test
  4. Create Test 对话框中,修改任何字段并选择要生成的任何方法,然后点击 OK
  5. Choose Destination Directory 对话框中,点击与要创建的测试类型对应的源集:对于插桩测试,请点击 androidTest;对于本地单元测试,请点击 test。然后,点击 OK

或者,您也可以在相应的测试源集中创建一个通用 Java 文件,具体操作步骤如下:

  1. 在左侧的 Project 窗口中,点击下拉菜单并选择 Project 视图。
  2. 展开相应的模块文件夹和嵌套的 src 文件夹。要添加本地单元测试,请展开 test 文件夹和嵌套的 java 文件夹;要添加插桩测试,请展开 androidTest 文件夹和嵌套的 java 文件夹。
  3. 右键点击 Java 软件包目录,然后依次选择 New > Java Class
  4. 为文件命名,然后点击 OK

此外,请务必在应用模块的 build.gradle 文件中指定测试库依赖项:

dependencies {
        // Required for local unit tests (JUnit 4 framework)
        testImplementation 'junit:junit:4.12'

        // Required for instrumented tests
        androidTestImplementation 'com.android.support:support-annotations:24.0.0'
        androidTestImplementation 'com.android.support.test:runner:0.5'
    }
    

如需了解其他可选库依赖项并详细了解如何编写测试,请参阅构建本地单元测试构建插桩单元测试

解决应用 APK 与测试 APK 之间的冲突

您的应用 APK 与其测试 APK 共享相同的类路径。因此,如果这两个 APK 依赖于同一库的不同版本,您可能会遇到编译错误。如果 Gradle 无法检测到此版本冲突,您的应用可能会在运行时出现意外行为或崩溃。

要了解详情,请阅读修复依赖项解析错误

创建编译变体的插桩测试

如果您的项目包含的编译变体具有唯一的源集,则您可能需要相应的插桩测试源集。在源集中创建与编译变体对应的插桩测试有助于保持测试代码的条理性,也便于您只运行适用于给定编译变体的测试。

要添加编译变体的测试源集,请按以下步骤操作:

  1. 在左侧的 Project 窗口中,点击下拉菜单并选择 Project 视图。
  2. 在相应的模块文件夹内,右键点击 src 文件夹,然后依次点击 New > Directory
  3. 对于目录名称,输入“androidTestVariantName”。例如,如果您有一个名为“MyFlavor”的编译变体,则目录名称应为“androidTestMyFlavor”。然后,点击 OK
  4. 右键点击新目录,然后依次点击 New > Directory
  5. 输入“java”作为目录名称,然后点击 OK

现在您就可以按照上述添加新测试的步骤向这个新源集添加测试了。当您到达 Choose Destination Directory 对话框时,请选择新的变体测试源集。

src/androidTest/ 源集中的插桩测试由所有编译变体共享。在为应用的“MyFlavor”变体编译测试 APK 时,Gradle 会将 src/androidTest/src/androidTestMyFlavor/ 源集合并起来。

例如,下表显示了插桩测试文件所在源集的路径与应用代码源集的路径之间的对应关系。

应用类的路径 对应插桩测试类的路径
src/main/java/Foo.java src/androidTest/java/AndroidFooTest.java
src/myFlavor/java/Foo.java src/androidTestMyFlavor/java/AndroidFooTest.java

Gradle 编译系统会合并和替换来自不同测试源集的文件,就像它对应用源集的处理方法一样。在本例中,“androidTestMyFlavor”源集中的 AndroidFooTest.java 文件替换了“androidTest”源集中的版本。如需详细了解如何合并源集,请参阅配置编译

您应该对应用和测试源集使用编译变体的另一个原因是,通过模拟依赖项创建封闭测试。也就是说,您可以为应用创建一个包含依赖项虚假实现(例如通常不可靠的网络请求或设备传感器数据)的产品特性,然后添加一个对应的模拟测试源集。如需了解详情,请参阅有关利用产品特性进行封闭测试的博文。

配置插桩测试清单设置

当 Gradle 编译您的测试 APK 时,它会自动生成 AndroidManifest.xml 文件并为其配置 <instrumentation> 节点。Gradle 为您配置此节点的原因之一,是要确保 targetPackage 属性指定了受测应用的正确软件包名称。您可以更改此节点的某些其他设置,方法是在测试源集中再创建一个清单文件,或配置模块级 build.gradle 文件,如以下代码示例所示。

    android {
      ...
      // Each product flavor you configure can override properties in the
      // defaultConfig {} block. To learn more, go to Configure product flavors.
      defaultConfig {
        ...
        // Specifies the application ID for the test APK.
        testApplicationId "com.test.foo"
        // Specifies the fully-qualified class name of the test instrumentation runner.
        testInstrumentationRunner "android.test.InstrumentationTestRunner"
        // If set to 'true', enables the instrumentation class to start and stop profiling.
        // If set to false (default), profiling occurs the entire time the instrumentation
        // class is running.
        testHandleProfiling true
        // If set to 'true', indicates that the Android system should run the instrumentation
        // class as a functional test. The default value is 'false'
        testFunctionalTest true
      }
    }
    ...
    

更改测试版本类型

默认情况下,所有测试均针对调试版本类型运行。您可以使用模块级 build.gradle 文件中的 testBuildType 属性将此更改为其他版本类型。例如,如果您要针对“staging”版本类型运行测试,请按以下代码段所示修改该文件。

android {
        ...
        testBuildType "staging"
    }
    

配置 Gradle 测试选项

通过 Android Plugin for Gradle,您可以为全部或部分单元测试指定特定选项。在模块级 build.gradle 文件中,可以使用 testOptions {} 代码块指定相关选项来更改 Gradle 运行所有测试的方式。

    android {
      ...
      // Encapsulates options for running tests.
      testOptions {
        // Changes the directory where Gradle saves test reports. By default, Gradle saves test reports
        // in the path_to_your_project/module_name/build/outputs/reports/ directory.
        // '$rootDir' sets the path relative to the root directory of the current project.
        reportDir "$rootDir/test-reports"
        // Changes the directory where Gradle saves test results. By default, Gradle saves test results
        // in the path_to_your_project/module_name/build/outputs/test-results/ directory.
        // '$rootDir' sets the path relative to the root directory of the current project.
        resultsDir "$rootDir/test-results"
      }
    }
    

要仅为本地单元测试指定选项,请配置 testOptions {} 内的 unitTests {} 代码块。

    android {
      ...
      testOptions {
        ...
        // Encapsulates options for local unit tests.
        unitTests {
          // By default, local unit tests throw an exception any time the code you are testing tries to access
          // Android platform APIs (unless you mock Android dependencies yourself or with a testing
          // framework like Mockito). However, you can enable the following property so that the test
          // returns either null or zero when accessing platform APIs, rather than throwing an exception.
          returnDefaultValues true

          // Encapsulates options for controlling how Gradle executes local unit tests. For a list
          // of all the options you can specify, read Gradle's reference documentation.
          all {
            // Sets JVM argument(s) for the test JVM(s).
            jvmArgs '-XX:MaxPermSize=256m'

            // You can also check the task name to apply options to only the tests you specify.
            if (it.name == 'testDebugUnitTest') {
              systemProperty 'debug', 'true'
            }
            ...
          }
        }
      }
    }
    

对插桩测试使用单独的测试模块

如果您希望某个模块专用于插桩测试并将代码的其余部分与测试隔离,您可以创建一个单独的测试模块并配置其编译,创建和配置方式与库模块类似。要创建测试模块,请按以下步骤操作:

  1. 创建一个库模块
  2. 模块级编译文件中,应用 com.android.test 插件而不是 com.android.library
  3. 同步您的项目

创建测试模块后,您可以在主源集或变体源集(例如 src/main/javasrc/variant/java)中添加您的测试代码。如果您的应用模块定义了多个产品特性,您可以在测试模块中重新创建这些特性,并且测试模块会使用变体感知型依赖项管理机制尝试测试目标模块中的匹配特性。

默认情况下,测试模块仅包含并测试 debug 变体。不过,您可以创建新的版本类型,使其与测试的应用项目匹配。要使测试模块测试其他版本类型(而不测试“debug”版本类型),请使用 VariantFilter 停用测试项目中的 debug 变体,如下所示:

android {
        variantFilter { variant ->
            if (variant.buildType.name.equals('debug')) {
                variant.setIgnore(true);
            }
        }
    }
    

如果您希望测试模块仅测试应用的某些特性或版本类型,则可以使用 matchingFallbacks 属性,仅测试要测试的变体。这也使得测试模块无需针对自身配置这些变体。

运行测试

要运行测试,请按以下步骤操作:

  1. 点击工具栏中的 Sync Project 图标 ,确保您的项目与 Gradle 同步。
  2. 通过以下某种方式来运行测试:
    • Project 窗口中,右键点击一个测试,然后点击 Run 图标
    • 在代码编辑器中,右键点击测试文件中的某个类或方法,然后点击 Run 图标 ,以测试该类中的所有方法或仅测试该方法。
    • 要运行所有测试,右键点击测试目录,然后点击 Run tests 图标

默认情况下,您的测试使用 Android Studio 的默认运行配置来运行。如果想要更改某些运行设置(如插桩测试运行程序和部署选项),您可以在 Run/Debug Configurations 对话框中修改运行配置(依次点击 Run > Edit Configurations)。

查看测试覆盖率

测试覆盖率工具适用于本地单元测试,其作用是跟踪单元测试所覆盖的应用代码的百分比和区域。使用测试覆盖率工具可确定您是否已对组成应用的元素、类、方法和代码行进行了充分测试。

运行单元测试的方法有多种,IntelliJ 的运行覆盖率分析页面对这些方法进行了介绍。以下过程说明了如何从编辑器中以内嵌方式运行单元测试:

  1. 双击要运行的单元测试。
  2. 在编辑器中,将光标置于要运行覆盖率分析的代码行中。
    • 如果将光标置于类声明中,将运行该类中的所有测试方法。
    • 如果将光标置于方法声明中,将运行该方法中的所有代码。
    • 如果将光标置于方法中的特定代码行上,将仅运行该代码行。
  3. 右键点击将光标置于的代码行。
  4. 在上下文菜单中,选择 Run test-name with coverage

    此时将显示覆盖率工具窗口

图 2 显示了用于测试加法、减法、乘法和除法的计算器单元测试的覆盖率工具窗口。

图 2. 查看应用的代码覆盖率百分比。

如需详细了解本地单元测试,请参阅构建本地单元测试

查看测试结果

当您运行 JUnit 或插桩测试时,结果将显示在 Run 窗口中。绿条表示所有测试都成功,红条表示至少有一个测试失败。图 3 显示了成功的测试运行。

图 3. 测试结果显示在“Run”窗口中。

Run 窗口中,测试显示在左侧的树状视图中,而当前测试套件的结果和消息显示在右侧的输出窗格中。您可以使用工具栏、上下文菜单和状态图标来管理测试结果,具体说明如下:

  1. 使用运行工具栏重新运行当前测试、停止当前测试、重新运行失败的测试(未显示,因为仅适用于单元测试)、暂停输出以及转储线程。
  2. 使用测试工具栏对测试结果进行过滤和排序。您还可以展开或收起节点、显示测试覆盖率以及导入或导出测试结果。
  3. 点击上下文菜单图标 以跟踪正在运行的测试、显示内嵌统计信息、滚动到堆栈轨迹、在异常时打开源代码、自动滚动到源代码,以及在测试运行完成时选择第一个失败的测试。
  4. 测试状态图标指示测试是出现了错误、被忽略、已失败、正在进行、已通过、已暂停、已终止还是未运行。
  5. 右键点击树状视图中的某一代码行以显示上下文菜单,您可以使用该菜单在调试模式下运行测试、打开测试源代码文件,或跳转到正在测试的源代码中的相应代码行。

如需详细了解 Run 窗口以及该窗口中的工具栏和上下文菜单,请参阅 IntelliJ 的“Test Runner”标签页面。

查看内嵌统计信息

要了解运行您的测试用了多长时间,请执行以下操作:

  1. 点击齿轮图标
  2. 在下拉列表中,选择 Show Inline Statistics

    经过的时间(以毫秒为单位)将显示在测试的右侧。

比较字符串

如果在单元测试中比较两个字符串对象后得到的结果是 assertEquals() 失败,您可以查看这两个字符串对象之间的差异,以找出导致失败的原因,具体操作步骤如下:

  1. 在输出窗格中,点击 Click to see difference 链接。
  2. Differences viewer 中,按照 IntelliJ 的文件差异查看器页面中所述浏览差异。

导出测试结果

您可以采用 XML 或 HTML 格式导出测试结果,具体操作步骤如下:

  1. 点击 Export Test Results 图标
  2. Export Test Results 对话框中,提供格式和输出信息,然后点击 OK

    导出的测试结果将保存到指定的文件夹。

导入测试结果

您可以导入已导出的测试结果,具体操作步骤如下:

  1. 点击 Import Test Results 图标
  2. 在下拉菜单中,选择要导入的文件。

    导入的测试结果将显示在 Run 窗口中。