Robolectric (v4.3)の導入で迷える子羊を俺が救う[ Android / testing ]


記事の初版からだいたい一年が経ちました

(特にセットアップ関係は)ここを見たほうがよいと思います。
Android Developers > ドキュメント > ガイド > 「ローカル単体テストを作成する」
https://developer.android.com/training/testing/unit-testing/local-unit-tests?hl=ja
依存関係系はページの先頭に書いてあって、実際のコードはフレームワークの依存関係を含めるあたりです。

java.lang.IllegalStateException: No instrumentation registered!で怒られたらAndroid Gradle Pluginを見直してみてくださいね。
https://stackoverflow.com/questions/53595837/androidx-no-instrumentation-registered-must-run-under-a-registering-instrumen

よいテストライフを👋

これ is 何

Androidのテスト環境のRobolectric v4.3 の導入を紹介する記事です。

Robolectric v4.0以降大幅に書き方が変化したもののそれに対応していない記事が多く、
今新規に導入をしようとした場合には半端なレベルの日本語作業者は全員地獄を見ると思ったので、
次なる犠牲者を出さないためにも記事にすることにしました。ちなみに僕は地獄を見ました。

手順

ビルド設定関連

書いてあるところ : Robolectric GETTING-STARTED

module-name/build.gradle
android {
  // ほかにも書いてある
    sourceSets {
      // ↓無ければ追記
        test.java.srcDirs += 'src/test/java'
        androidTest.java.srcDirs += 'src/androidTest/java'
    }

    testOptions {
        unitTests {
            includeAndroidResources = true
            returnDefaultValues = true
        }
    }
}

dependencies{
    // ほかにも書いてある
    testImplementation 'androidx.test:core:1.2.0'
    testImplementation 'androidx.test:runner:1.2.0'
    testImplementation 'androidx.test:rules:1.2.0'
    testImplementation 'androidx.test.ext:junit:1.1.1'
    testImplementation 'androidx.test.ext:truth:1.2.0'
    testImplementation 'com.google.truth:truth:0.42'

    testImplementation 'junit:junit:4.12'
    testImplementation 'org.robolectric:robolectric:4.3'
}
gradle.properties
  # androidStudio 3.3以上では不要
  android.enableUnitTestBinaryResources=true

ここまでで環境の整備は完了です。かんたん!

テストの記述

手作業でも AndroidStudio のCreate Testでもよいので、テストのファイルを用意します。(test.java.srcDirsのほうのディレクトリです!)
ここで色々書いていくわけですが、公式を参考にするだけでは書ききれなかったりと、ハマりポイントでした。

テストランナー

@RunWith(AndroidJUnit4::class)

v4.0以降を使うのであれば、たぶんandroidxを使わない手はないと思うのでAndroidJUnit4です。
Robolectricのものと自動で切り替わります。

Contextの取得

ApplicationProvider.getApplicationContext()

InstrumentationRegistryは使用しません。

ActivityScenarioの取得

val scenario = launch<CountActivity>(startActivityIntent)

実際の実装からいくらか切り抜いて例としているので半端になってしまうのですが、
こんな感じ↓のダイアログのイメージです。まさかの文字列表現。

タイトル文字列   
[-][+][ 5]

各ViewのIDはtitleText,buttonMinus,buttonPlus,count

機能は

  • 数値をカウントアップ・ダウンする
  • Intent経由で以下の値を渡せる
    • タイトル文字列 IntentKey.TITLE_TEXT (String)
    • 現在値 IntentKey.COUNT (Int)
    • 最小値 IntentKey.LOWEST_COUNT (Int)
@RunWith(AndroidJUnit4::class)
@Config(sdk = [Build.VERSION_CODES.O]) // 動作対象バージョンの指定ができます
class CountActivityTest {
    @Test fun onClickCount() {
        val startActivityIntent = Intent(ApplicationProvider.getApplicationContext(), CountActivity::class.java).also{
            it.putExtra(CountActivity.IntentKey.TITLE_TEXT.name,"TEST_TITLE")
            it.putExtra(CountActivity.IntentKey.COUNT.name, 3)
            it.putExtra(CountActivity.IntentKey.LOWEST_COUNT.name, 0)
        }

        val scenario = launch<CountActivity>(startActivityIntent)
        scenario.moveToState(Lifecycle.State.CREATED) // Activityの状態をCreatedにする=onCreate()後の状態
        scenario.onActivity {
            assertNotEquals(it.titleText.text, "ERROR_TITLE")
            assertEquals(it.titleText.text, "TEST_TITLE")

            assertEquals(it.count.text, "3")
            assertNotEquals(it.count.text, "2")
            it.buttonMinus.performClick()
            assertEquals(it.count.text, "2")
            it.buttonMinus.performClick()
            it.buttonMinus.performClick()
            assertEquals(it.count.text, "0")
            it.buttonMinus.performClick()
            assertEquals(it.count.text, "0")

            it.buttonPlus.performClick()
            assertEquals(it.count.text, "1")
        }
    }
}

JVM上でActivity関連のテストができました。

既知の不具合(踏んだもの)

おわりに

なかなかv4.4のリリースありませんね・・・(2020.04.23)

happy testing !