当启动插桩测试时,系统会重启其目标软件包,并且会注入和启动插桩代码以执行测试。一种例外情况是,这里的目标软件包不能是 Android 应用框架本身,例如软件包 android
,因为这样做会导致出现一种矛盾情况:需要重启 Android 框架,而正是该框架支持系统功能,包括插桩本身。
这意味着,插桩测试无法将本身注入到 Android 框架(也称为系统服务器)以执行测试。为了测试 Android 框架,测试代码只能调用公共 API surface,或调用平台源代码树中使用 Android 接口定义语言 (AIDL) 公开的 API surface。对于此类测试,针对任何特定软件包都没有意义。因此,按照惯例,此类插桩会声明为针对自身的测试应用软件包,如其 <manifest>
标签 AndroidManifest.xml
中的定义一样。
根据要求,此类测试应用软件包还可以:
- 捆绑测试所需的 activity。
- 与系统共享用户 ID。
- 使用平台密钥进行签名。
- 根据框架源代码而不是公共 SDK 进行编译。
此类插桩测试有时称为自插桩。以下是平台源代码中自插桩测试的一些示例:
本文介绍的示例是编写新的插桩测试,其中目标软件包设置为其自己的测试应用软件包。本指南使用以下测试作为示例:
建议您先浏览代码以获得粗略的印象,然后再继续。
确定源代码所在的位置
通常,您的团队已有既定的放置模式,在既定的位置检入代码,并且在既定的位置添加测试。大多数团队拥有单个 git 代码库,或与其他团队共享一个代码库,但有一个包含组件源代码的专用子目录。
假设组件源代码的根位置是在 <component source
root>
,大多数组件在该位置下具有 src
和 tests
文件夹,以及一些一些其他文件(如 Android.mk
,或拆分为额外的.mk
文件)、清单文件 AndroidManifest.xml
,以及测试配置文件“AndroidTest.xml”。
由于您要添加全新的测试,因此或许需要在组件 src
旁边创建 tests
目录,并为其填充内容。
在某些情况下,您的团队可能会在 tests
下设置更深的目录结构,因为需要将不同的测试套件打包到单独的 APK 中。这种情况下,您需要在 tests
下创建一个新的子目录。
不管结构如何,您最终都需要在 tests
目录或新建的子目录中填充类似示例 gerrit 更改中 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()
返回的属性相同,而且也是您通过 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" />
所有插桩测试都必须采用此设置,因为相关的类打包在一个单独的框架 JAR 库文件中,因此在应用框架调用测试软件包时,需要额外的类路径条目。
android:targetPackage="android.test.example.helloworld"
您可能已经注意到,上述代码声明的 targetPackage
与此文件 manifest
标记中声明的 package
属性相同。如测试基础知识中所述,此类插桩测试通常用于测试框架 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 中有许多目标准备器可供开发者使用,这些目标准备器可用于确保在测试执行之前正确地设置设备。
<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 更改包含 JUnit4 功能的一些非常基本的用法。有关示例,请参阅 /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
注解来执行测试前设置和测试后拆解。同样,JUnit4 可以在方法上使用 @BeforeClass
和 @AfterClass
注解,以便在执行测试类中的所有测试之前执行设置,并在执行测试类中的所有测试之后执行拆解。请注意,类作用域的设置和拆解方法必须是静态方法。对于测试方法,与早期版本的 JUnit 不同,它们不再需要让方法名称以 test
开头,而是要求每种方法都必须带有 @Test
注解。跟以往一样,测试方法必须公开、不声明任何返回值、不接受任何参数,并且可能会抛出异常。
插桩类访问权限
虽然在基本的 Hello World 示例中未提及,但 Android 测试需要访问 Instrumentation
实例这种情况相当普遍:该实例是核心 API 接口,可以提供对应用上下文、与 activity 生命周期相关的测试 API 等内容的访问权限。
因为 JUnit4 测试不再需要通用基类,所以不再需要通过 InstrumentationTestCase#getInstrumentation()
获取 Instrumentation
实例,新的测试运行程序会通过 InstrumentationRegistry
(用于存储插桩框架创建的上下文和环境设置)管理该实例。
如需访问 Instrumentation
类的实例,只需在 InstrumentationRegistry
类上调用静态方法 getInstrumentation()
:
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation()
在本地构建和测试
对于最常见的用例,请使用 Atest。
对于需要更繁琐自定义设置的更复杂用例,请遵循插桩说明。