针对应用的插桩测试示例

此类插桩测试与针对常规 Android 应用的测试没有什么不同。值得注意的是,包含插桩的测试应用需要与其针对的应用使用相同的证书进行签名。

请注意,本指南假定您已掌握平台源代码树工作流的一些相关知识。如果没有,请参阅 https://source.android.com/source/requirements。本文介绍的示例是编写新的插桩测试,其中目标软件包设置为其自己的测试应用软件包。如果您不熟悉相关概念,请仔细阅读平台测试简介

本指南使用以下测试作为示例:

  • frameworks/base/packages/Shell/tests

建议您先浏览代码以获得粗略的印象,然后再继续。

确定源代码所在的位置

由于此插桩测试将针对应用,因此惯例是将测试源代码放在平台源代码树中组件源代码根目录下的 tests 目录中。

有关源代码所在位置的更多说明,请参阅自插桩测试的端到端示例

清单文件

就像常规应用一样,每个插桩测试模块都需要一个清单文件。如果您将该文件命名为 AndroidManifest.xml 并在 Android.mk 旁边为测试 tmodule 提供该文件,则 BUILD_PACKAGE 核心 makefile 将自动包含该文件。

在继续深入阅读以下内容之前,强烈建议您先查阅应用清单概览

此文档概述了清单文件的基本组成部分及其功能。

要获得示例 gerrit 更改的最新版清单文件,请访问:https://android.googlesource.com/platform/frameworks/base/+/master/packages/Shell/tests/AndroidManifest.xml

为方便起见,下面附上快照:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.shell.tests">

    <application>
        <uses-library android:name="android.test.runner" />

        <activity
            android:name="com.android.shell.ActionSendMultipleConsumerActivity"
            android:label="ActionSendMultipleConsumer"
            android:theme="@android:style/Theme.NoDisplay"
            android:noHistory="true"
            android:excludeFromRecents="true">
            <intent-filter>
                <action android:name="android.intent.action.SEND_MULTIPLE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="*/*" />
            </intent-filter>
        </activity>
    </application>

    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
        android:targetPackage="com.android.shell"
        android:label="Tests for Shell" />

</manifest>

关于清单文件的一些说明:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.shell.tests">

package 属性是应用软件包名称:它是 Android 应用框架用来标识应用(在此上下文中即您的测试应用)的唯一标识符。系统中的每个用户只能安装一个采用该软件包名称的应用。

由于这是一个测试应用软件包,独立于接受测试的应用软件包,因此必须使用不同的软件包名称:一个常见的惯例是添加后缀 .test

此外,此 package 属性与 ComponentName#getPackageName() 返回的属性相同,而且也与用来通过 adb shell 与各种 pm 子命令进行交互的属性相同。

另请注意,虽然该软件包名称通常与 Java 软件包名称的样式相同,但是实际上两者之间没有什么关系。换句话说,您的应用(或测试)软件包可能包含具有任何软件包名称的类,但另一方面,您可以选择保持简洁性,使应用或测试中的顶级 Java 软件包名称与应用软件包名称完全相同。

<uses-library android:name="android.test.runner" />

所有插桩测试都必须采用此设置,因为相关的类打包在一个单独的框架 jar 库文件中,因此在应用框架调用测试软件包时,需要额外的类路径条目。

android:targetPackage="com.android.shell"

上述代码将插桩的目标软件包设置为 com.android.shell.tests。通过 am instrument 命令调用插桩时,框架将重启 com.android.shell.tests 进程,并将插桩代码注入该进程以执行测试。这也意味着,测试代码可以访问在接受测试的应用中运行的所有类实例,并且或许能够操纵状态,具体取决于公开的测试钩子。

简单配置文件

每个新的测试模块都必须具有配置文件,以使用模块元数据、编译时依赖项和打包指令来指引编译系统。在大多数情况下,基于 Soong 的 Blueprint 文件选项就足够了。如需了解详情,请参阅简单的测试配置

复杂配置文件

对于更复杂的测试,您还需要为 Android 的自动化测试框架 Trade Federation 编写测试配置文件。

测试配置可以指定特殊的设备设置选项和默认参数来提供测试类。

要获得示例 gerrit 更改的最新版配置文件,请访问:https://android.googlesource.com/platform/frameworks/base/+/master/packages/Shell/tests/AndroidTest.xml

为方便起见,下面附上快照:

<configuration description="Runs Tests for Shell.">
    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
        <option name="test-file-name" value="ShellTests.apk" />
    </target_preparer>

    <option name="test-suite-tag" value="apct" />
    <option name="test-tag" value="ShellTests" />
    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
        <option name="package" value="com.android.shell.tests" />
        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
    </test>
</configuration>

关于测试配置文件的一些说明:

<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
  <option name="test-file-name" value="ShellTests.apk"/>
</target_preparer>

上述代码告知 Trade Federation 使用指定的 target_preparer 将 ShellTests.apk 安装到目标设备上。Trade Federation 中有许多目标准备器可供开发者使用,这些目标准备器可用于确保在测试执行之前正确地设置设备。

<test class="com.android.tradefed.testtype.AndroidJUnitTest">
  <option name="package" value="com.android.shell.tests"/>
  <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/>
</test>

上述代码指定要用于执行测试的 Trade Federation 测试类,并传入设备上要执行的软件包,以及测试运行器框架(在本例中为 JUnit)。

有关测试模块配置的更多信息,请参阅此处

JUnit4 功能

通过使用 android-support-test 库作为测试运行器,可以采用新的 JUnit4 样式测试类,并且示例 gerrit 更改包含 JUnit4 功能的一些非常基本的用法。

要获得示例 gerrit 更改的最新源代码,请访问:frameworks/base/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.javast.java

虽然测试模式通常特定于组件团队,但有一些普遍有用的使用模式。

@SmallTest
@RunWith(AndroidJUnit4.class)
public final class FeatureFactoryImplTest {

JUnit4 的一个显著区别是,不再需要从通用测试基类继承测试,而是在普通 Java 类中编写测试并使用注解来指示某些测试设置和约束。在本例中,我们指示此类应作为 Android JUnit4 测试运行。

@SmallTest 注解指定了整个测试类的测试大小:添加到此测试类中的所有测试方法都继承此测试大小注解。测试前类设置、测试后拆解和测试后类拆解:类似于 JUnit4 中的 setUptearDown 方法。 Test 注解用于对实际测试进行注解。

    @Before
    public void setup() {
    ...
    @Test
    public void testGetProvider_shouldCacheProvider() {
    ...

JUnit4 在方法上使用 @Before 注解来执行测试前设置。还有用于执行测试后拆解的 @After,不过在本例中未使用。同样,JUnit4 可以在方法上使用 @BeforeClass@AfterClass 注解,以便在执行测试类中的所有测试之前执行设置,并在执行测试类中的所有测试之后执行拆解。请注意,类作用域的设置和拆解方法必须是静态方法。

对于测试方法,与早期版本的 JUnit 不同,它们不再需要使方法名称以 test 开头,而是每种方法都必须带有 @Test 注解。像往常一样,测试方法必须公开、不声明任何返回值、不接受任何参数,并且可能会抛出异常。

        Context context = InstrumentationRegistry.getTargetContext();

因为 JUnit4 测试不再需要通用基类,所以不再需要采用基类方法通过 getContext()getTargetContext() 来获取 Context 实例,新的测试运行器会通过 InstrumentationRegistry(用于存储插桩框架创建的上下文和环境设置)管理这些实例。通过此类,您还可以调用:

  • getInstrumentation()Instrumentation 类的实例
  • getArguments():通过 -e <key> <value> 传递给 am instrument 的命令行参数

在本地编译和测试

对于最常见的用例,请使用 Atest

对于需要更繁琐自定义设置的更复杂用例,请遵循插桩说明