| <html devsite><head> |
| |
| <meta name="book_path" value="/_book.yaml"/> |
| |
| <meta name="project_path" value="/_project.yaml"/> |
| </head> |
| <body> |
| |
| <!-- |
| Copyright 2018 The Android Open Source Project |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| --> |
| |
| <h1 id="self-instrumenting_tests_example" class="page-title">自插桩测试示例</h1> |
| |
| <p>当启动插桩测试时,系统会重启其目标软件包,并且会注入和启动插桩代码以执行测试。一种例外情况是,这里的目标软件包不能是 Android 应用框架本身,即软件包 <code>android</code>,因为这样做会导致出现一种矛盾情况:需要重启 Android 框架,而正是该框架支持系统功能,包括插桩本身。</p> |
| |
| <p>这意味着,插桩测试无法将本身注入到 Android 框架(也称为系统服务器)以执行测试。为了测试 Android 框架,测试代码只能调用公共 API Surface,或者通过平台源代码树中可用的 Android 接口定义语言 (<a href="https://developer.android.com/guide/components/aidl.html" class="external">AIDL</a>) 公开的 API Surface。对于此类测试,针对任何特定软件包都没有意义。因此,按照惯例会将此类插桩声明为针对其自己的测试应用软件包,如其自己的 <code>AndroidManifest.xml</code> 中的 <code><manifest></code> 标记所定义。</p> |
| |
| <p>根据要求,此类测试应用软件包还可以:</p> |
| |
| <ul> |
| <li>捆绑测试所需的 Activity。</li> |
| <li>与系统共享用户 ID。</li> |
| <li>使用平台密钥进行签名。</li> |
| <li>根据框架源代码而不是公共 SDK 进行编译。</li> |
| </ul> |
| |
| <p>此类插桩测试有时称为自插桩。以下是平台源代码中自插桩测试的一些示例:</p> |
| |
| <ul> |
| <li><a href="https://android.googlesource.com/platform/frameworks/base/+/master/core/tests/">frameworks/base/core/tests/</a></li> |
| <li><a href="https://android.googlesource.com/platform/frameworks/base/+/master/services/tests/servicestests/">frameworks/base/services/tests/servicestests</a></li> |
| </ul> |
| |
| <p>本文介绍的示例是编写新的插桩测试,其中目标软件包设置为其自己的测试应用软件包。本指南使用以下测试作为示例:</p> |
| |
| <ul> |
| <li><a href="https://android.googlesource.com/platform/platform_testing/+/master/tests/example/instrumentation/">Hello World 插桩测试</a></li> |
| </ul> |
| |
| <p>建议您先浏览代码以获得粗略的印象,然后再继续。</p> |
| |
| <h2 id="deciding_on_a_source_location">确定源代码所在的位置</h2> |
| |
| <p>通常,您的团队已有既定的放置模式,在既定的位置检入代码,并且在既定的位置添加测试。大多数团队拥有单个 git 代码库,或与其他团队共享一个代码库,但有一个包含组件源代码的专用子目录。</p> |
| |
| <p>假设组件源代码的根位置是在 <code><component source |
| root></code>,大多数组件在该位置下具有 <code>src</code> 和 <code>tests</code> 文件夹,以及一些其他文件(如 <code>Android.mk</code>,或拆分为额外的 <code>.mk</code> 文件)、清单文件 <code>AndroidManifest.xml</code>,以及测试配置文件“AndroidTest.xml”。</p> |
| |
| <p>由于您要添加全新的测试,因此或许需要在组件 <code>src</code> 旁边创建 <code>tests</code> 目录,并为其填充内容。</p> |
| |
| <p>在某些情况下,您的团队可能会在 <code>tests</code> 下设置更深的目录结构,因为需要将不同的测试套件打包到单独的 apk 中。在这种情况下,您需要在 <code>tests</code> 下创建一个新的子目录。</p> |
| |
| <p>不管是什么样的结构,您最终都需要在 <code>tests</code> 目录或新建子目录中添加文件,并且文件应类似于示例 gerrit 更改中的 <code>instrumentation</code> 目录中的文件。下面几部分将进一步详细说明各个文件。</p> |
| |
| <h2 id="manifest_file">清单文件</h2> |
| |
| <p>就像常规应用一样,每个插桩测试模块都需要一个清单文件。如果您将该文件命名为 <code>AndroidManifest.xml</code> 并在 <code>Android.mk</code> 旁边为测试 tmodule 提供该文件,则 <code>BUILD_PACKAGE</code> 核心 makefile 将自动包含该文件。</p> |
| |
| <p>在继续深入阅读以下内容之前,强烈建议您先查阅<a href="https://developer.android.com/guide/topics/manifest/manifest-intro.html" class="external">应用清单概览</a>。</p> |
| |
| <p>此文档概述了清单文件的基本组成部分及其功能。有关示例,请参阅 <a href="https://android.googlesource.com/platform/platform_testing/+/master/tests/example/instrumentation/AndroidManifest.xml">platform_testing/tests/example/instrumentation/AndroidManifest.xml</a>。</p> |
| |
| <p>为方便起见,下面附上快照:</p> |
| <pre class="prettyprint lang-xml"><code><manifest xmlns:android="http://schemas.android.com/apk/res/android" |
| package="android.test.example.helloworld" |
| |
| <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="21" /> |
| |
| <application> |
| <uses-library android:name="android.test.runner" /> |
| </application> |
| |
| <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" |
| android:targetPackage="android.test.example.helloworld" |
| android:label="Hello World Test"/> |
| |
| </manifest> |
| </code></pre> |
| <p>关于清单文件的一些说明:</p> |
| <pre class="prettyprint lang-xml"><code><manifest xmlns:android="http://schemas.android.com/apk/res/android" |
| package="android.test.example.helloworld" |
| </code></pre> |
| <p><code>package</code> 属性是应用软件包名称:它是 Android 应用框架用来标识应用(在此上下文中即您的测试应用)的唯一标识符。系统中的每个用户只能安装一个采用该软件包名称的应用。</p> |
| |
| <p>此外,此 <code>package</code> 属性与 <a href="https://developer.android.com/reference/android/content/ComponentName.html#getPackageName()"><code>ComponentName#getPackageName()</code></a> 返回的属性相同,而且也与用来通过 <code>adb shell</code> 与各种 <code>pm</code> 子命令进行交互的属性相同。</p> |
| |
| <p>另请注意,虽然该软件包名称通常与 Java 软件包名称的样式相同,但是实际上两者之间没有什么关系。换句话说,您的应用(或测试)软件包可能包含具有任何软件包名称的类,但另一方面,您可以选择保持简洁性,使应用或测试中的顶级 Java 软件包名称与应用软件包名称完全相同。</p> |
| <pre class="prettyprint lang-xml"><code>android:sharedUserId="android.uid.system" |
| </code></pre> |
| <p>此代码声明在安装时,应该为此 apk 授予与核心平台相同的用户 ID,即运行时身份。请注意,这取决于是否使用与核心平台相同的证书为 apk 签名(请参阅上一部分中的 <code>LOCAL_CERTIFICATE</code>),不过它们是不同的概念:</p> |
| |
| <ul> |
| <li>某些权限或 API 受签名保护,这需要相同的签名证书</li> |
| <li>某些权限或 API 需要调用者的 <code>system</code> 用户身份,如果调用软件包与核心平台本身不同,则需要该软件包与 <code>system</code> 共享用户 ID</li> |
| </ul> |
| <pre class="prettyprint lang-xml"><code><uses-library android:name="android.test.runner" /> |
| </code></pre> |
| <p>所有插桩测试都必须采用此设置,因为相关的类打包在一个单独的框架 jar 库文件中,因此在应用框架调用测试软件包时,需要额外的类路径条目。</p> |
| <pre class="prettyprint lang-xml"><code>android:targetPackage="android.test.example.helloworld" |
| </code></pre> |
| <p>您可能已经注意到,上述代码声明的 <code>targetPackage</code> 与此文件的 <code>manifest</code> 标记中声明的 <code>package</code> 属性相同。如<a href="../basics/index.md">测试基础知识</a>中所述,此类插桩测试通常用于测试框架 API,所以除了它们本身之外,拥有特定的目标应用软件包并不是很有意义。</p> |
| |
| <h2 id="simple_configuration_file">简单配置文件</h2> |
| |
| <p>每个新的测试模块都必须具有配置文件,以使用模块元数据、编译时依赖项和打包指令来指引编译系统。在大多数情况下,基于 Soong 的 Blueprint 文件选项就足够了。如需了解详情,请参阅<a href="blueprints.md">简单的测试配置</a>。</p> |
| |
| <h2 id="complex_configuration_file">复杂配置文件</h2> |
| <aside class="special"><strong>重要提示</strong>:<span>只有 CTS 测试或需要特殊设置(如停用蓝牙或收集示例数据)的测试需要遵循本部分中的说明。其他所有用例均可通过<a href="blueprints">简单的测试配置</a>来涵盖。如需了解适用于本部分的更多详细信息,请参阅<a href="test-config">复杂的测试配置</a>。</span></aside> |
| <p>对于这些更复杂的用例,您还需要为 Android 的自动化测试框架 <a href="/devices/tech/test_infra/tradefed/">Trade Federation</a> 编写测试配置文件。</p> |
| |
| <p>测试配置可以指定特殊的设备设置选项和默认参数来提供测试类。有关示例,请参阅 <a href="/platform_testing/+/master/tests/example/instrumentation/AndroidTest.xml">/platform_testing/tests/example/instrumentation/AndroidTest.xml</a>。</p> |
| |
| <p>为方便起见,下面附上快照:</p> |
| <pre class="prettyprint lang-xml"><code><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> |
| </code></pre> |
| <p>关于测试配置文件的一些说明:</p> |
| <pre class="prettyprint lang-xml"><code><target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> |
| <option name="test-file-name" value="HelloWorldTests.apk"/> |
| </target_preparer> |
| </code></pre> |
| <p>上述代码告知 Trade Federation 使用指定的 target_preparer 将 HelloWorldTests.apk 安装到目标设备上。Trade Federation 中有许多目标准备器可供开发者使用,这些目标准备器可用于确保在测试执行之前正确地设置设备。</p> |
| <pre class="prettyprint lang-xml"><code><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> |
| </code></pre> |
| <p>上述代码指定要用于执行测试的 Trade Federation 测试类,并传入设备上要执行的软件包,以及测试运行器框架(在本例中为 JUnit)。</p> |
| |
| <p>有关测试模块配置的更多信息,请参阅<a href="test-config.md">此处</a>。</p> |
| |
| <h2 id="junit4_features">JUnit4 功能</h2> |
| |
| <p>通过使用 <code>android-support-test</code> 库作为测试运行器,可以采用新的 JUnit4 样式测试类,并且示例 gerrit 更改包含 JUnit4 功能的一些非常基本的用法。有关示例,请参阅 <a href="https://android.googlesource.com/platform/platform_testing/+/master/tests/example/instrumentation/src/android/test/example/helloworld/HelloWorldTest.java">/platform_testing/tests/example/instrumentation/src/android/test/example/helloworld/HelloWorldTest.java</a>。</p> |
| |
| <p>虽然测试模式通常特定于组件团队,但有一些普遍有用的使用模式。</p> |
| <pre class="prettyprint lang-java"><code>@RunWith(JUnit4.class) |
| public class HelloWorldTest { |
| </code></pre> |
| <p>JUnit4 的一个显著区别是,不再需要从通用测试基类继承测试,而是在普通 Java 类中编写测试并使用注解来指示某些测试设置和约束。在本例中,我们指示此类应作为 JUnit4 测试运行。</p> |
| <pre class="prettyprint lang-java"><code> @BeforeClass |
| public static void beforeClass() { |
| ... |
| @AfterClass |
| public static void afterClass() { |
| ... |
| @Before |
| public void before() { |
| ... |
| @After |
| public void after() { |
| ... |
| @Test |
| @SmallTest |
| public void testHelloWorld() { |
| ... |
| </code></pre> |
| <p>JUnit4 在方法上使用 <code>@Before</code> 和 <code>@After</code> 注解来执行测试前设置和测试后拆解。同样,JUnit4 可以在方法上使用 <code>@BeforeClass</code> 和 <code>@AfterClass</code> 注解,以便在执行测试类中的所有测试之前执行设置,并在执行测试类中的所有测试之后执行拆解。请注意,类作用域的设置和拆解方法必须是静态方法。对于测试方法,与早期版本的 JUnit 不同,它们不再需要使方法名称以 <code>test</code> 开头,而是每种方法都必须带有 <code>@Test</code> 注解。像往常一样,测试方法必须公开、不声明任何返回值、不接受任何参数,并且可能会抛出异常。</p> |
| |
| <p><strong>重要提示</strong>:测试方法本身带有 <code>@Test</code> 注解;请注意,对于要通过 APCT 执行的测试,它们必须带有测试大小注解:在本例中,将 <code>testHelloWorld</code> 方法注解为 <code>@SmallTest</code>。该注解可以在方法作用域或类作用域内应用。</p> |
| |
| <h2 id="accessing_instrumentation">访问 <code>instrumentation</code></h2> |
| |
| <p>虽然在基本的 Hello World 示例中未涉及,但是有一种相当普遍的情况是,Android 测试需要访问 <code>Instrumentation</code> 实例:这是核心 API 接口,可提供对应用上下文、Activity 生命周期相关测试 API 等内容的访问权限。</p> |
| |
| <p>因为 JUnit4 测试不再需要通用基类,所以不再需要通过 <code>InstrumentationTestCase#getInstrumentation()</code> 获取 <code>Instrumentation</code> 实例,新的测试运行器会通过 <a href="https://developer.android.com/reference/android/support/test/InstrumentationRegistry.html"><code>InstrumentationRegistry</code></a>(用于存储插桩框架创建的上下文和环境设置)管理该实例。</p> |
| |
| <p>要访问 <code>Instrumentation</code> 类的实例,只需在 <code>InstrumentationRegistry</code> 类上调用静态方法 <code>getInstrumentation()</code>:</p> |
| <pre class="prettyprint lang-java"><code>Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation() |
| </code></pre> |
| <h2 id="build_and_test_locally">在本地编译和测试</h2> |
| |
| <p>对于最常见的用例,请使用 <a href="/compatibility/tests/development/atest">Atest</a>。</p> |
| |
| <p>对于需要更繁琐自定义设置的更复杂用例,请遵循<a href="instrumentation.md">插桩说明</a>。</p> |
| |
| </body></html> |