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

インストゥルメンテーション テストが開始されると、対象パッケージが再起動され、インストゥルメンテーション コードが挿入されて実行が開始されます。例外の 1 つは、ここでのターゲット パッケージが Android アプリケーション フレームワーク自体(パッケージ 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、テスト構成ファイル「AndroidText.xml」があります。

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

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

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

マニフェスト ファイル

通常のアプリケーションと同様に、各インストゥルメンテーション テスト モジュールにはマニフェスト ファイルが必要です。ファイル名を AndroidManifest.xml にしてテスト モジュール用の Android.mk と同じ場所に置くと、BUILD_PACKAGE コアの makefile によって自動的に取り入れられます。

先に進む前に、まずアプリ マニフェストの概要の確認をおすすめします。

マニフェスト ファイルの基本コンポーネントとその機能の概要がわかります。サンプルについては、platform_testing/tests/example/instrumentation/AndroidManifest.xml をご覧ください。

説明のため、スナップショットを以下に示します。

<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>
    

マニフェスト ファイルで、いくつか注目する点があります。

<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 をアノテーションとして追加する必要があります。通常、テストメソッドはパブリックであり、戻り値を宣言せず、パラメータを受け取らず、例外をスローする場合があります。

重要: テストメソッド自体に @Test アノテーションが付加されています。APCT 経由で実行するテストの場合、テストサイズのアノテーションを付加する必要があります。たとえば、上記メソッド testHelloWorld では @SmallTest がアノテーションとして付加されています。アノテーションは、メソッド スコープまたはクラススコープで適用できます。

instrumentation へのアクセス

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

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

Instrumentation クラスのインスタンスにアクセスするには、InstrumentationRegistry クラスの静的メソッド getInstrumentation() を呼び出すだけです。

Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation()
    

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

最も一般的な使用例の場合、Atest を使用します。

より複雑なカスタマイズが必要な場合は、インストゥルメンテーションの手順に従います。