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

ローカルテストは、Android デバイスやエミュレータではなく、独自のワークステーションで直接実行されます。そのため、テストの実行には Android デバイスではなく、ローカルの Java 仮想マシン(JVM)が使用されます。ローカルテストを使用すると、アプリのロジックをより迅速に評価できます。ただし、Android フレームワークを操作できないため、実行できるテストの種類に制限が生じます。

単体テストでは、コードの小さなセクション(テスト対象のユニット)の動作を検証します。これは、そのコードを実行して結果を確認することで行われます。

通常、単体テストはシンプルですが、テスト対象のユニットがテストのしやすさを考慮して設計されていない場合、セットアップが問題になることがあります。

  • 検証するコードは、テストからアクセス可能である必要があります。たとえば、プライベート メソッドを直接テストすることはできません。代わりに、公開 API を使用してクラスをテストします。
  • 単体テストを分離して実行するには、テスト対象のユニットの依存関係を、フェイクやその他のテストダブルなど、制御するコンポーネントに置き換える必要があります。これは、コードが Android フレームワークに依存している場合に特に問題となります。

Android での一般的な単体テスト戦略については、テスト対象をご覧ください。

ローカルテストの場所

デフォルトでは、ローカル単体テストのソースファイルは module-name/src/test/ に配置されます。このディレクトリは、Android Studio を使用して新しいプロジェクトを作成するときにすでに存在しています。

テスト用の依存関係の追加

また、JUnit テスト フレームワークが提供する標準 API を使用するように、プロジェクトのテスト依存関係を構成する必要もあります。

これを行うには、アプリのモジュールの build.gradle ファイルを開き、次のライブラリを依存関係として指定します。testImplementation 関数を使用して、アプリではなく、ローカルのテスト ソースセットに適用されることを示します。

dependencies {
  // Required -- JUnit 4 framework
  testImplementation "junit:junit:$jUnitVersion"
  // Optional -- Robolectric environment
  testImplementation "androidx.test:core:$androidXTestVersion"
  // Optional -- Mockito framework
  testImplementation "org.mockito:mockito-core:$mockitoVersion"
  // Optional -- mockito-kotlin
  testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
  // Optional -- Mockk framework
  testImplementation "io.mockk:mockk:$mockkVersion"
}

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

ローカル単体テストクラスは、JUnit 4 テストクラスとして作成します。

これを行うには、1 つ以上のテストメソッドを含むクラスを作成します(通常は module-name/src/test/ 内)。テストメソッドは @Test アノテーションで始まり、テスト対象のコンポーネントの 1 つの側面を実行して検証するコードが含まれています。

次の例は、ローカル単体テストクラスを実装する方法を示しています。テストメソッド emailValidator_correctEmailSimple_returnsTrue() は、アプリ内のメソッドである isValidEmail() を検証しようとします。isValidEmail() も true を返した場合、テスト関数は true を返します。

Kotlin

import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test

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

}

Java

import org.junit.Test;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

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

アプリのコンポーネントが想定される結果を返すかどうかを評価する、読みやすいテストを作成する必要があります。junit.Assert、Hamcrest、Truth などのアサーション ライブラリを使用することをおすすめします。上記のスニペットは、junit.Assert の使用方法の例です。

モック可能な Android ライブラリ

ローカル単体テストを実行すると、Android Gradle プラグインは、Android フレームワークのすべての API を含むライブラリを対象に含めます。このライブラリは、プロジェクトで使用されているバージョンに適合しています。このライブラリには、それらの API のすべてのパブリック メソッドとクラスが含まれていますが、メソッド内のコードは削除されています。いずれかのメソッドにアクセスすると、テストは例外をスローします。

これにより、Context などの Android フレームワークのクラスを参照するときに、ローカルテストをビルドできます。さらに重要なのは、Android クラスでモック フレームワークを使用できることです。

Android の依存関係をモックする

一般的な問題は、クラスが文字列リソースを使用していることが判明することです。文字列リソースを取得するには、Context クラスの getString() メソッドを呼び出します。ただし、ローカルテストでは、Context やそのメソッドは Android フレームワークに属しているため、使用できません。理想的には、getString() の呼び出しをクラスから移動することですが、これが常に実用的であるとは限りません。解決策は、getString() メソッドが呼び出されたときに常に同じ値を返す Context のモックまたはスタブを作成することです。

モック可能な Android ライブラリと、Mockito や MockK などのモック フレームワークを使用すると、単体テストで Android クラスのモックの動作をプログラムできます。

Mockito を使用してローカル単体テストにモック オブジェクトを追加するには、次のプログラミング モデルに従います。

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

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

import android.content.Context
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.junit.MockitoJUnitRunner
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock

private const val FAKE_STRING = "HELLO WORLD"

@RunWith(MockitoJUnitRunner::class)
class MockedContextTest {

  @Mock
  private lateinit var mockContext: Context

  @Test
  fun readStringFromContext_LocalizedString() {
    // Given a mocked Context injected into the object under test...
    val mockContext = mock<Context> {
        on { getString(R.string.name_label) } doReturn FAKE_STRING
    }

    val myObjectUnderTest = ClassUnderTest(mockContext)

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

    // ...then the result should be the expected one.
    assertEquals(result, FAKE_STRING)
  }
}

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

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

モック可能な Android ライブラリは、Error: "Method ... not mocked メッセージを使用してそのメソッドにアクセスしようとすると、例外をスローします。

スローされた例外がテストで問題となる場合は、戻り値の型に応じて、メソッドが null またはゼロを返すように動作を変更できます。これを行うには、Groovy のプロジェクトの最上位 build.gradle ファイルに次の構成を追加します。

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