当 Instrumentation 测试启动时,Instrumentation 代码会被注入到其目标软件包中并启动执行,同时目标软件包也会重启。一个例外是,此处的目标软件包不能是 Android 应用框架本身(例如软件包 android
),因为这样做会导致自相矛盾的情况,即 Android 框架需要重启,而 Android 框架是支持系统功能(包括 Instrumentation 本身)的基础。
这意味着 instrumentation 测试无法将其自身注入到 Android 框架(也称为系统服务器)中执行。为了测试 Android 框架,测试代码只能调用公共 API 表面,或者使用 Android 接口定义语言 AIDL 公开的 API 表面,这些 API 表面在平台源代码树中可用。对于此类测试,定位任何特定软件包没有意义。因此,按照惯例,此类 instrumentation 会被声明为以其自身的测试应用程序软件包为目标,如其自身的 <manifest>
标记(位于 AndroidManifest.xml
中)中所定义的那样。
根据需求,此类别中的测试应用程序软件包也可能
- 捆绑测试所需的 activity。
- 与系统共享用户 ID。
- 使用平台密钥签名。
- 针对框架源代码而不是公共 SDK 进行编译。
此类 instrumentation 测试有时被称为自 instrumentation。以下是平台源代码中自 instrumentation 测试的一些示例
此处介绍的示例是编写新的 instrumentation 测试,并将目标软件包设置为其自身的测试应用程序软件包。本指南使用以下测试作为示例
建议先浏览代码以获得大致印象,然后再继续。
确定源位置
通常,您的团队已经建立了一套检查代码和添加测试的模式。大多数团队拥有单个 git 存储库,或者与其他团队共享一个存储库,但有一个专门的子目录,其中包含组件源代码。
假设您的组件源的根位置在 <component source root>
,则大多数组件在其下都有 src
和 tests
文件夹,以及一些其他文件,例如 Android.mk
(或分解为其他 .mk
文件)、清单文件 AndroidManifest.xml
和测试配置文件 'AndroidTest.xml'。
由于您要添加全新的测试,因此您可能需要创建 tests
目录(与您的组件 src
相邻),并使用内容填充它。
在某些情况下,由于需要将不同的测试套件打包到单独的 apk 中,您的团队可能在 tests
下有更深层的目录结构。在这种情况下,您需要在 tests
下创建一个新的子目录。
无论结构如何,您最终都会使用类似于示例 gerrit 更改中的 instrumentation
目录中的文件来填充 tests
目录或新创建的子目录。每个文件的详细信息将在本文档的后面部分进行说明。
清单文件
与应用项目一样,每个 instrumentation 测试模块都需要一个名为 AndroidManifest.xml
的清单文件。要使用 BUILD_PACKAGE
核心 makefile 自动包含此文件,请将此文件放在测试模块的 Android.mk
文件旁边。
如果您不熟悉 AndroidManifest.xml
文件,请参阅应用清单概览
以下是一个 AndroidManifest.xml
文件示例
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:sharedUserId="android.uid.system"
package="android.test.example.helloworld" >
<application>
<uses-library android:name="android.test.runner"/>
</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()
返回的内容相同,也与您用于与各种 pm
子命令交互的 adb shell
相同。
请注意,尽管软件包名称的样式通常与 Java 软件包名称相同,但实际上它与 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
属性相同。正如测试基础知识中提到的那样,此类 instrumentation 测试通常旨在测试框架 API,因此对于它们来说,拥有特定的目标应用程序软件包(而不是其自身)没有太大的意义。
简单配置文件
每个新的测试模块都必须有一个配置文件,以使用模块元数据、编译时依赖项和打包说明来指导构建系统。在大多数情况下,基于 Soong 的 Blueprint 文件选项就足够了。有关详细信息,请参阅简单测试配置。
复杂配置文件
对于这些更复杂的情况,您还需要为 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 中有许多 target preparer 可供开发人员使用,这些 preparer 可用于确保设备在测试执行之前已正确设置。
<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() {
...
@Before
和 @After
注释由 JUnit4 在方法上使用,以执行测试前设置和测试后拆卸。类似地,@BeforeClass
和 @AfterClass
注释由 JUnit4 在方法上使用,以在执行测试类中的所有测试之前执行设置,并在之后执行拆卸。请注意,类范围的设置和拆卸方法必须是静态的。至于测试方法,与早期版本的 JUnit 不同,它们不再需要以 test
开头方法名称,相反,它们中的每一个都必须使用 @Test
进行注释。与往常一样,测试方法必须是 public 的,不声明返回值,不带参数,并且可以抛出异常。
Instrumentation 类访问
尽管基本 hello world 示例中未涵盖,但 Android 测试通常需要访问 Instrumentation
实例:这是核心 API 接口,提供对应用程序上下文、活动生命周期相关测试 API 等的访问。
由于 JUnit4 测试不再需要公共基类,因此不再需要通过 InstrumentationTestCase#getInstrumentation()
获取 Instrumentation
实例,相反,新的测试运行程序通过 InstrumentationRegistry
管理它,其中存储了 instrumentation 框架创建的上下文和环境设置。
要访问 Instrumentation
类的实例,只需在 InstrumentationRegistry
类上调用静态方法 getInstrumentation()
即可
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation()
在本地构建和测试
对于最常见的用例,请使用Atest。
对于需要更复杂定制的更复杂情况,请按照instrumentation 说明进行操作。