自己インストルメンテーション テストの例

インストルメンテーション テストが開始されると、対象パッケージが再起動され、インストルメンテーション コードが挿入されて実行が開始されます。ただし、1 つの例外として、ここでのターゲット パッケージを Android アプリケーション フレームワーク自体(パッケージ android など)にすることはできません。そのようにすると、インストルメンテーション自体を含むシステム機能をサポートしている Android フレームワークを再起動しなければならないという逆説的な状況に陥るためです。

つまり、インストルメンテーション テストは Android フレームワーク、つまりシステム サーバーに自分自身を挿入して実行することはできません。Android フレームワークをテストするために、テストコードは公開 API サーフェスのみ、またはプラットフォーム ソースツリーで利用可能な Android インターフェース定義言語(AIDL)を使用して公開されたもののみを呼び出すことができます。このカテゴリのテストの場合、特定のパッケージをターゲットにすることは意味がありません。したがって、AndroidManifest.xml 独自の <manifest> タグで定義されているように、独自のテスト アプリケーション パッケージをターゲットとするよう宣言するのが慣例です。

このカテゴリのテスト アプリケーション パッケージでは、要件に応じて以下の内容も行うことができます。

  • テストに必要なアクティビティをバンドルする。
  • ユーザー ID をシステムと共有する。
  • プラットフォーム キーで署名する。
  • 公開 SDK ではなく、フレームワーク ソースに対してコンパイルする。

このカテゴリのインストルメンテーション テストは、「自己インストルメンテーション」と呼ばれることもあります。プラットフォーム ソースでの自己インストルメンテーション テストの例を次に示します。

ここでは、独自のテスト アプリケーション パッケージをターゲット パッケージに設定して新しいインストルメンテーション テストを作成する方法を説明します。このガイドでは、次のテストを例として使用します。

先に進む前に、まずコードを確認して、おおよそのイメージをつかむことをおすすめします。

ソースの場所を決定する

通常、コードをチェックインする場所とテストを追加する場所はすでにチームで決まっています。ほとんどのチームは git リポジトリを 1 つ所有しているか、他のチームと共有しているが専用のサブ ディレクトリ(コンポーネントのソースコードが含まれる)を持っています。

コンポーネントのソースのルートが <component source root> であると仮定すると、ほとんどのコンポーネントではその下に src フォルダと tests フォルダ、Android.mk(または複数に分割された .mk ファイル)などの追加ファイル、マニフェスト ファイル AndroidManifest.xml、テスト構成ファイル「AndroidTest.xml」があります。

新しいテストを追加する場合、コンポーネントの src と同じ並びに tests ディレクトリを作成し、データを追加する必要があります。

場合によっては、さまざまなテストスイートを個々の apk にパッケージ化する必要があるため、tests 配下にさらにディレクトリ構造を作成することもあります。ここでは、tests の下に新しいサブ ディレクトリを作成する必要があります。

構造にかかわらず、サンプルにおける gerrit 変更の instrumentation ディレクトリ下にあるファイルと同様のファイルを tests ディレクトリや新しく作成されたサブ ディレクトリに追加します。各ファイルについては、この後で詳しく説明します。

マニフェスト ファイル

アプリ プロジェクトと同様に、各インストルメンテーション テスト モジュールには、AndroidManifest.xml というマニフェスト ファイルが必要です。コアの makefile である BUILD_PACKAGE を使用してこのファイルを自動的に取り込むには、テスト モジュール用の 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 アプリケーション フレームワークがアプリケーション(ここではテスト アプリケーション)を識別するために使用する一意の識別子です。システム内の各ユーザーは、このパッケージ名のアプリケーションを 1 つのみインストールできます。

さらに、この package 属性は ComponentName#getPackageName() が返すものと同一であり、adb shell を介してさまざまな pm サブコマンドで使用するパッケージとも同一です。

なお、パッケージ名は通常 Java パッケージ名と同じスタイルですが、実際にはほとんど関係ありません。つまり、アプリケーション(またはテスト)のパッケージには任意のパッケージ名のクラスを含めることができますが、単純にアプリケーションやテストの最上位 Java パッケージ名をアプリケーション パッケージ名と同一にすることもできます。

android:sharedUserId="android.uid.system"

これは、この APK ファイルにコア プラットフォームと同じユーザー ID(ランタイム ID)がインストール時に付与されることを宣言します。これは、APK がコア プラットフォームと同じ証明書で署名されているかどうかに依存しますが(上記セクションの LOCAL_CERTIFICATE を参照)、概念は以下のように異なります。

  • 一部の権限または API は署名で保護されているため、同じ署名証明書が必要です。
  • 一部の権限や API では呼び出し元の system ユーザー ID が必要になります。呼び出し元のパッケージがコア プラットフォーム自体とは別のパッケージである場合、呼び出し元のパッケージはユーザー ID を system と共有する必要があります。
<uses-library android:name="android.test.runner" />

これは、すべてのインストルメンテーション テストに必要です。なぜなら、関連するクラスが別のフレームワーク 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>

これは、指定した target_preparer を使用して、HelloWorldTests.apk を対象デバイスにインストールするように Trade Federation に指示します。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() {
    ...

@Before@After のアノテーションは、JUnit4 によってメソッドで使用され、テスト前のセットアップとテスト後のティアダウンを行います。同様に、@BeforeClass@AfterClass のアノテーションは JUnit4 によりメソッドで使用され、テストクラス内のすべてのテストを実行する前にセットアップを行い、後でティアダウンします。クラススコープのセットアップ メソッドとティアダウン メソッドは静的である必要があります。テストメソッドでは、以前の JUnit とは異なり、メソッド名を test で開始する必要はありませんが、代わりに各メソッドに @Test をアノテーションとして追加する必要があります。通常、テストメソッドはパブリックであり、戻り値を宣言せず、パラメータを受け取らず、例外をスローする場合があります。

インストルメンテーション クラスのアクセス

基本的な Hello World の例では説明していませんが、Android テストで Instrumentation インスタンスへのアクセスが必要になることはよくあります。このインスタンスは、アプリケーション コンテキスト、アクティビティのライフサイクル関連のテスト API などへのアクセスを提供するコア API インターフェースです。

JUnit4 テストでは、共通の基本クラスが不要になったため、InstrumentationTestCase#getInstrumentation() を使用して Instrumentation インスタンスを取得する必要がなくなりました。代わりに、新しいテストランナーは InstrumentationRegistry でこのインスタンスを管理します。ここには、インストルメンテーション フレームワークで作成されたコンテキストと環境のセットアップが保存されます。

Instrumentation クラスのインスタンスにアクセスするのに必要な操作は、InstrumentationRegistry クラスの静的メソッド getInstrumentation() を呼び出すことのみです。

Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation()

ローカルでのビルドとテスト

最も一般的なユースケースでは、Atest を使用します。

より多くのカスタマイズが必要な複雑なケースでは、インストルメンテーションの手順を実施してください。