ローカル単体テストを作成する

テストを短時間で実施しなければならず、実際のデバイス上で行うテストのような再現性や信頼性を必要としない場合は、ローカル単体テストによりアプリのロジックを評価できます。この種のテストでは、通常 Robolectric またはモック フレームワーク(Mockito など)を使って依存関係が成立するようにします。多くの場合、どちらのツールを使用するかは、テストに含まれる依存関係のタイプによって決まります。

  • Android フレームワークとの依存関係(特にフレームワークと複雑なやり取りが生じるもの)がある場合は、Robolectric を使用してフレームワークとの依存関係を対象とするほうが適切です。
  • Android フレームワークとの依存関係が極めて少ない場合や、テストが独自のオブジェクトのみに依存している場合は、Mockito のようなモック フレームワークを使用してモックとの依存関係を対象にする対応で十分です。

テスト環境をセットアップする

Android Studio プロジェクトでは、ローカル単体テストのソースファイルを module-name/src/test/java/ に保存する必要があります。このディレクトリは、新しいプロジェクトの作成時から存在しています。

また、JUnit 4 フレームワークの標準 API を使用するために、プロジェクト用のテスト依存関係を構成する必要があります。テストで Android との依存関係を扱う必要がある場合は、ローカル単体テストを簡素化するために Robolectric または Mockito ライブラリを含めます。

アプリの最上位の build.gradle ファイルの中で、依存関係として次のライブラリを指定します。

    dependencies {
        // Required -- JUnit 4 framework
        testImplementation 'junit:junit:4.12'
        // Optional -- Robolectric environment
        testImplementation 'androidx.test:core:1.0.0'
        // Optional -- Mockito framework
        testImplementation 'org.mockito:mockito-core:1.10.19'
    }
    

ローカル単体テストクラスを作成する

ローカル単体テストクラスは、JUnit 4 テストクラスとして作成します。 JUnit は、Java で最も広く利用されている単体テスト フレームワークです。JUnit 4 では以下を行う必要がないため、以前のバージョンよりも簡明かつ柔軟な方法でテストを記述できます。

  • junit.framework.TestCase クラスの拡張。
  • テストメソッド名への 'test' キーワード(接頭辞)の付加。
  • junit.framework パッケージまたは junit.extensions パッケージのクラスの使用。

基本的な JUnit 4 テストクラスを作成するには、1 つ以上のテストメソッドを含むクラスを作成します。1 つのテストメソッドは @Test アノテーションで始まり、そこにはテスト対象コンポーネント内の単一の機能を実施、検証するためのコードが含まれています。

次の例は、ローカル単体テストクラスを実装する方法を示しています。テストメソッド emailValidator_CorrectEmailSimple_ReturnsTrue により、テスト対象アプリの isValidEmail() メソッドが正しい結果を返すことを確認しています。

Kotlin

    import com.google.common.truth.Truth.assertThat
    import org.junit.Test

    class EmailValidatorTest {
        @Test
        fun emailValidator_CorrectEmailSimple_ReturnsTrue() {
            assertThat(EmailValidator.isValidEmail("name@email.com")).isTrue()
        }
    }
    

Java

    import com.google.common.truth.Truth.assertThat;
    import org.junit.Test;

    public class EmailValidatorTest {
        @Test
        public void emailValidator_CorrectEmailSimple_ReturnsTrue() {
            assertThat(EmailValidator.isValidEmail("name@email.com")).isTrue();
        }
    }
    

アプリのコンポーネントから期待する結果が返されるかどうかを評価するための、読み取り可能なテストを作成するには、上記の例のように Truth ライブラリと Android Assertions のクラスを使用するようおすすめします。Truth および Android Assertions がサポートするロジック検証の種類について詳しくは、読みやすいアサーションを作成する方法のセクションをご覧ください。

ただし、期待する結果と実際の結果を比較するために junit.Assert メソッドや Hamcrest マッチャーis() メソッドや equalTo() メソッドなど)を使用する場合は、代わりにこれらのライブラリを使用しても問題ありません。

注: Hamcrest は、Espresso の ViewMatcher クラスなどのマッチャーを作成する場合にも推奨されるライブラリです。

フレームワークの依存関係を含める

テストで Android フレームワークの複数の依存関係とやり取りしたり、依存関係とのやり取りが複雑だったりする場合は、AndroidX Test の Robolectric アーティファクトを使用します。Robolectric では、ローカル JVM や実際のデバイスで、実際の Android フレームワーク コードとネイティブ フレームワークの模擬コードが実行できます。

次の例は、Robolectric を使用する単体テストの作成方法を示しています。

app/build.gradle

    android {
        // ...
        testOptions {
            unitTests.includeAndroidResources = true
        }
    }
    

MyLocalUnitTestClass

Kotlin

    import android.content.Context
    import androidx.test.core.app.ApplicationProvider
    import com.google.common.truth.Truth.assertThat
    import org.junit.Test

    private const val FAKE_STRING = "HELLO_WORLD"

    class UnitTestSample {
        val context = ApplicationProvider.getApplicationContext<Context>()

        @Test fun readStringFromContext_LocalizedString() {
            // Given a Context object retrieved from Robolectric...
            val myObjectUnderTest = ClassUnderTest(context)

            // ...when the string is returned from the object under test...
            val result: String = myObjectUnderTest.getHelloWorldString()

            // ...then the result should be the expected one.
            assertThat(result).isEqualTo(FAKE_STRING)
        }
    }
    

Java

    import android.content.Context;
    import androidx.test.core.app.ApplicationProvider;
    import org.junit.Test;

    import static com.google.common.truth.Truth.assertThat;

    public class UnitTestSampleJava {
        private static final String FAKE_STRING = "HELLO_WORLD";
        private Context context = ApplicationProvider.getApplicationContext();

        @Test
        public void readStringFromContext_LocalizedString() {
            // Given a Context object retrieved from Robolectric...
            ClassUnderTest myObjectUnderTest = new ClassUnderTest(context);

            // ...when the string is returned from the object under test...
            String result = myObjectUnderTest.getHelloWorldString();

            // ...then the result should be the expected one.
            assertThat(result).isEqualTo(FAKE_STRING);
        }
    }
    

Android Builder クラスを含める

Robolectric 環境や実際のデバイス上で実行するローカル単体テストを作成する場合は、いくつかの一般的なフレームワーク クラスのために用意された AndroidX Test のビルダーを使用できます。このビルダーを使えば、モックやリフレクションを使用せずに以下のクラスのインスタンスを作成できます。

Parcelables ユーティリティ クラスを使用する

さらに、このライブラリには Parcelable オブジェクト用のユーティリティ クラスが用意されています。このクラスには Creator オブジェクトが用意されており、指定された Parcelable オブジェクトをアンマーシャルし、重複する Parcelable オブジェクトをマーシャルします。

注: アンマーシャルや再マーシャルの操作が成功したかどうかを確認するには、メソッドで Parcelables.forceParcel() を呼び出す必要があります。

モックの依存関係を含める

Android Plug-in for Gradle では、デフォルト設定の場合、実際のコードが含まれないように変更されたバージョンの android.jar ライブラリを使ってローカル単体テストが実行されます。単体テストでメソッドから Android のクラスが呼び出されても、例外がスローされます。これは、Android プラットフォーム(明示的にビルドまたはモックしていない部分)のいかなる動作の影響も受けずに、作成したコードのみをテストできるようにするためです。

Android の依存関係のモック

Android の依存関係がほとんどなく、テスト対象がアプリ内のコンポーネントとその依存関係との処理に限定されている場合には、モック フレームワークを使用してコードの外部との依存関係をスタブで代用します。そうすれば、コンポーネントと依存関係とのやり取りが期待どおりに行われるかを簡単にテストできます。Android の依存関係をモック オブジェクトに置き換えることで、単体テストを他の Android システムから切り離しつつ、依存関係について正しいメソッドが呼び出されていることの確認もできます。Java 用の Mockito モック フレームワーク(バージョン 1.9.5 以降)には、Android 単体テストと互換性があります。Mockito を使用して、呼び出されたときに指定の値を返すようにモック オブジェクトを構成できます。

このフレームワークを使用してローカル単体テストにモック オブジェクトを追加するには、次のガイドに沿ってプログラミングを行います。

  1. テスト環境のセットアップに示されているとおり、build.gradle ファイルに Mockito ライブラリの依存関係を含めます。
  2. 単体テストクラスの定義の最初に、@RunWith(MockitoJUnitRunner.class) アノテーションを追加します。このアノテーションは、Mockito テストランナーに対して、フレームワークの使用が正しいことを検証し、モック オブジェクトの初期化を簡素化するように伝えるものです。
  3. Android の依存関係用のモック オブジェクトを作成するには、フィールド宣言の前に @Mock アノテーションを追加します。
  4. 依存関係の動作をスタブで代用するには、when() および thenReturn() メソッドを使用して、状況に合った条件と戻り値を指定します。

次の例は、モックの Context オブジェクトを使用する単体テストの作成方法を示しています。

Kotlin

    import android.content.Context
    import com.google.common.truth.Truth.assertThat
    import org.junit.Test
    import org.junit.runner.RunWith
    import org.mockito.Mock
    import org.mockito.Mockito.`when`
    import org.mockito.junit.MockitoJUnitRunner

    private const val FAKE_STRING = "HELLO WORLD"

    @RunWith(MockitoJUnitRunner::class)
    class UnitTestSample {

        @Mock
        private lateinit var mockContext: Context

        @Test
        fun readStringFromContext_LocalizedString() {
            // Given a mocked Context injected into the object under test...
            `when`(mockContext.getString(R.string.hello_word))
                    .thenReturn(FAKE_STRING)
            val myObjectUnderTest = ClassUnderTest(mockContext)

            // ...when the string is returned from the object under test...
            val result: String = myObjectUnderTest.getHelloWorldString()

            // ...then the result should be the expected one.
            assertThat(result, `is`(FAKE_STRING))
        }
    }
    

Java

    import android.content.Context;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.mockito.Mock;
    import org.mockito.junit.MockitoJUnitRunner;

    import static com.google.common.truth.Truth.assertThat;
    import static org.mockito.Mockito.when;

    @RunWith(MockitoJUnitRunner.class)
    public class UnitTestSample {

        private static final String FAKE_STRING = "HELLO WORLD";

        @Mock
        Context mockContext;

        @Test
        public void readStringFromContext_LocalizedString() {
            // Given a mocked Context injected into the object under test...
            when(mockContext.getString(R.string.hello_world))
                    .thenReturn(FAKE_STRING);
            ClassUnderTest myObjectUnderTest = new ClassUnderTest(mockContext);

            // ...when the string is returned from the object under test...
            String result = myObjectUnderTest.getHelloWorldString();

            // ...then the result should be the expected one.
            assertThat(result, is(FAKE_STRING));
        }
    }
    

Mockito フレームワークの使用の詳細については、Mockito API リファレンスサンプルコードSharedPreferencesHelperTest クラスをご覧ください。また、Android テスト コードラボもお試しください。

エラー: 「Method ... not mocked」

テスト実行中に、モックしていない Android SDK の API を呼び出すと、メソッドがモックされていないというエラーが表示されます。これは、単体テストの実行に使用される android.jar ファイルには実際のコードが含まれていないためです(Android SDK の API は、デバイス上の Android システム イメージからのみ提供されます)。

この場合、デフォルトでは各メソッドで例外がスローされます。これは、単体テストで Android プラットフォーム(Mockito などで明示的にモックしていない部分)のいかなる動作の影響も受けずに、作成したコードのみをテストできるようにするためです。

スローされた例外によってテストに問題が生じる場合は、代わりにメソッドで null またはゼロを返すように動作を変更できます。そのためには、プロジェクトの最上位の build.gradle ファイルに以下の構成を追加します。

    android {
      ...
      testOptions {
        unitTests.returnDefaultValues = true
      }
    }
    

注意事項: returnDefaultValues プロパティを true に設定する場合は注意が必要です。戻り値を null やゼロにすることで、テストの有効性が落ちる場合があります。この場合、デバッグが難しくなり、失敗するはずのテストが成功する可能性もあります。最終手段としてのみ使用してください。

ローカル単体テストを実行する

ローカル単体テストを実行するには、次の手順を行います。

  1. ツールバーの [Sync Project] をクリックして、プロジェクトを Gradle と同期します(必須)。
  2. 次のいずれかの方法でテストを実行します。
    • 単一のテストを実行するには、[Project] ウィンドウを開き、対象のテストを右クリックして [Run] をクリックします。
    • クラス内のすべてのメソッドをテストするには、テストファイル内の対象のクラスまたはメソッドを右クリックして [Run] をクリックします。
    • ディレクトリ内のすべてのテストを実行するには、対象のディレクトリを右クリックして [Run tests] をクリックします。

これで、Android Plugin for Gradle により、デフォルト ディレクトリ( src/test/java/ )にあるローカル単体テストのコードがコンパイルされ、テストアプリがビルドされた後、デフォルトのテストランナー クラスを使用してローカルで実行されます。その後、結果が Android Studio の [Run] ウィンドウに表示されます。