自我检测测试示例

当仪器测试开始时,其目标包将重新启动,并注入仪器代码并启动执行。一个例外是这里的目标包不能是 Android 应用程序框架本身,即包android ,因为这样做会导致 Android 框架需要重新启动的矛盾情况,这是支持系统功能的,包括检测本身。

这意味着插桩测试无法将自身注入到 Android 框架(即系统服务器)中执行。为了测试 Android 框架,测试代码只能调用公共 API 表面,或通过平台源代码树中可用的 Android 接口定义语言AIDL公开的那些。对于此类测试,针对任何特定包没有意义。因此,通常声明此类检测以其自己的测试应用程序包为目标,如其自己的AndroidManifest.xml<manifest>标记中所定义。

根据要求,此类别中的测试应用程序包还可以:

  • 捆绑测试所需的活动。
  • 与系统共享用户 ID。
  • 使用平台密钥进行签名。
  • 针对框架源而非公共 SDK 进行编译。

此类仪器测试有时称为自仪器测试。以下是平台源代码中的一些自检测示例:

此处涵盖的示例是编写一个新的仪器测试,目标包设置在其自己的测试应用程序包中。本指南使用以下测试作为示例:

建议先浏览一下代码,在继续之前获得一个大概的印象。

确定源位置

通常,您的团队已经建立了用于签入代码的位置和用于添加测试的位置的既定模式。大多数团队拥有一个单独的 git 存储库,或者与其他团队共享一个,但有一个包含组件源代码的专用子目录。

假设您的组件源的根位置在<component source root> ,大多数组件下面都有srctests文件夹,以及一些额外的文件,例如Android.mk (或分解成额外的.mk文件),清单文件AndroidManifest.xml和测试配置文件“AndroidTest.xml”。

由于您正在添加一个全新的测试,您可能需要在组件src旁边创建tests目录,并用内容填充它。

在某些情况下,由于需要将不同的测试套件打包到单独的 apk 中,您的团队可能有更多的目录结构正在tests中。在这种情况下,您需要在tests下创建一个新的子目录。

无论结构如何,您最终都会使用与示例 gerrit 更改中的instrumentation目录中的文件类似的文件填充tests目录或新创建的子目录。以下部分将详细解释每个文件。

清单文件

就像常规应用程序一样,每个仪器测试模块都需要一个清单文件。如果您将该文件命名为AndroidManifest.xml并将其提供给您的测试模块的Android.mk旁边,它将自动包含在BUILD_PACKAGE核心 makefile 中。

在继续之前,强烈建议先阅读App Manifest Overview

这给出了清单文件的基本组件及其功能的概述。请参阅platform_testing/tests/example/instrumentation/AndroidManifest.xml中的示例。

为方便起见,此处包含快照:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="android.test.example.helloworld" >

    <application/>

    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                     android:targetPackage="android.test.example.helloworld"
                     android:label="Hello World Test"/>

</manifest>

清单文件中的一些选择说明:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="android.test.example.helloworld" >

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

此外,此package属性与ComponentName#getPackageName()返回的内容相同,也与您通过adb shell与各种pm子命令交互所使用的相同。

另请注意,虽然包名称通常与 Java 包名称具有相同的样式,但实际上与它没有什么关系。换句话说,您的应用程序(或测试)包可能包含具有任何包名称的类,但另一方面,您可以选择简单性并在您的应用程序或测试中使用与应用程序包名称相同的顶级 Java 包名称。

android:sharedUserId="android.uid.system"

这声明在安装时,此 apk 应被授予与核心平台相同的用户 ID,即运行时身份。请注意,这取决于使用与核心平台相同的证书签名的 apk(请参阅上一节中的LOCAL_CERTIFICATE ),但它们是不同的概念:

  • 某些权限或 API 受签名保护,这需要相同的签名证书
  • 某些权限或 API 需要调用者的system用户身份,这需要调用包与system共享用户 ID,如果它是与核心平台本身分开的包
<uses-library android:name="android.test.runner" />

这是所有 Instrumentation 测试所必需的,因为相关类打包在单独的框架 jar 库文件中,因此当应用程序框架调用测试包时需要额外的类路径条目。

android:targetPackage="android.test.example.helloworld"

您可能已经注意到,此处的targetPackage声明与此文件的manifest标记中声明的package属性相同。正如测试基础中提到的,这类仪器测试通常用于测试框架 API,因此对于他们来说,拥有特定的目标应用程序包不是很有意义,而不是本身。

简单配置文件

每个新的测试模块都必须有一个配置文件,以使用模块元数据、编译时依赖项和打包指令来指导构建系统。在大多数情况下,基于 Soong 的蓝图文件选项就足够了。有关详细信息,请参阅简单测试配置

复杂的配置文件

对于这些更复杂的情况,您还需要为 Android 的测试工具Trade Federation编写测试配置文件。

测试配置可以指定特殊的设备设置选项和默认参数来提供测试类。请参阅/platform_testing/tests/example/instrumentation/AndroidTest.xml中的示例。

为方便起见,此处包含快照:

<configuration description="Runs sample instrumentation test.">
  <target_preparer class="com.android.tradefed.targetprep.TestFilePushSetup"/>
  <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
    <option name="test-file-name" value="HelloWorldTests.apk"/>
  </target_preparer>
  <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"/>
  <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"/>
  <option name="test-suite-tag" value="apct"/>
  <option name="test-tag" value="SampleInstrumentationTest"/>

  <test class="com.android.tradefed.testtype.AndroidJUnitTest">
    <option name="package" value="android.test.example.helloworld"/>
    <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="HelloWorldTests.apk"/>
</target_preparer>

这会告知 Trade Federation 使用指定的 target_preparer 将 HelloWorldTests.apk 安装到目标设备上。 Trade Federation 中的开发人员可以使用许多目标准备器,这些可用于确保在测试执行之前正确设置设备。

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

这指定了用于执行测试的 Trade Federation 测试类,并传入要执行的设备上的包和测试运行器框架,在本例中为 JUnit。

有关详细信息,请参阅测试模块配置

JUnit4 特性

使用android-support-test库作为测试运行器可以采用新的 JUnit4 样式测试类,示例 gerrit 更改包含一些非常基本的功能使用。请参阅/platform_testing/tests/example/instrumentation/src/android/test/example/helloworld/HelloWorldTest.java中的示例。

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

@RunWith(JUnit4.class)
public class HelloWorldTest {

JUnit4 中的一个显着差异是测试不再需要从公共测试基类继承;相反,您在纯 Java 类中编写测试并使用注释来指示某些测试设置和约束。在此示例中,我们指示此类应作为 JUnit4 测试运行。

    @BeforeClass
    public static void beforeClass() {
    ...
    @AfterClass
    public static void afterClass() {
    ...
    @Before
    public void before() {
    ...
    @After
    public void after() {
    ...
    @Test
    @SmallTest
    public void testHelloWorld() {
    ...

JUnit4 在方法上使用@Before@After注释来执行测试前设置和测试后拆卸。类似地,@ @AfterClass @BeforeClass被 JUnit4 用于方法,以在执行测试类中的所有测试之前执行设置,并在之后执行拆卸。请注意,类范围的设置和拆卸方法必须是静态的。至于测试方法,与早期版本的 JUnit 不同,它们不再需要以test开头的方法名称,而是每个方法都必须使用@Test注解。像往常一样,测试方法必须是公共的,声明没有返回值,没有参数,并且可能会抛出异常。

重要:测试方法本身用@Test注释进行注释;并注意,对于通过 APCT 执行的测试,它们必须用测试大小进行注释:示例注释方法testHelloWorld@SmallTest 。注释可以应用于方法范围或类范围。

访问instrumentation

虽然基本的 hello world 示例中没有涉及,但 Android 测试需要访问Instrumentation实例是相当普遍的:这是提供对应用程序上下文、活动生命周期相关测试 API 等的访问的核心 API 接口。

因为 JUnit4 测试不再需要公共基类,所以不再需要通过InstrumentationTestCase#getInstrumentation()获取Instrumentation实例,相反,新的测试运行器通过InstrumentationRegistry管理它,其中存储了由仪器框架创建的上下文和环境设置。

要访问Instrumentation类的实例,只需在InstrumentationRegistry类上调用静态方法getInstrumentation()即可:

Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation()

在本地构建和测试

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

对于需要更多定制的更复杂的情况,请遵循检测说明