【Android】JUnit5が動くところまでの手順【テスト】

AndroidプロジェクトでUnitテストを追加するAndroid

久々にAndroid系の記事を投稿します。
自作TwitterクライアントTwitMorse 〜モールス信号でつぶやこう〜
では今までテストコードが存在しませんでした。理由としては個人で開発しているアプリであるため、別にバグがあっても自分で修正できてしまうという点がありましたが、さすがに仕様を忘れたりするのと、自習がてらにテストコードを追加できるところまで実装しようと思いました。そのことを今回は記事にします。


JUnit5でUIテストが書けるようになる

JUnitはJavaの代表的なテスティングフレームワークで、長らくJUnit4が主流として活躍してきました。2017年にJUnit5が発表されて以降、徐々に浸透しつつあるテスティングフレームワークです。

フルKotlin化したアプリでJavaのテストフレームワークが使えるのか?という疑問ですが、KotlinはそもそもJVM(Java Virtual Machine Java仮想環境)で動作する言語であり、JVM言語として位置づけられるため、いくつかおまじないを唱える(Gradle設定をする)とKotlinアプリでもJUnit5を用いたテストが書けるようになります。

動作環境

システム要件
– System Requments
– Android Gradle Plugin 3.2.0 or higher
– Gradle 4.7 or higher
– Java 8
– Android 8.0/API 26/Oreo or higher

参考資料

以下、TwitMorse 〜モールス信号でつぶやこう〜
でJUnit5を導入した時の環境です。

  • Kotlin 1.3.50
  • Java 1.8_152 (Java 1.8 = Java 8)
  • Android Studio 3.5
  • Android Gradle Plugin 3.5.0
  • Gradle 4.4.1 (システム要件は満たしていないが動いた)

Gradleのバージョンがシステム要件を満たしているのか?という点については後々調べるとして、とりあえず動くところまで行けたのでその手順の紹介をしていきます。

1. android-junit5プラグインの導入

プロジェクトルートのbuild.gradle

buildscript {  
    dependencies {
        // 1) Add android-junit5 plugin to project
        classpath "de.mannodermaus.gradle.plugins:android-junit5:1.4.2.0"
    }
}

2. appのbuild.gradle

android {
    defaultConfig {
        // ...
        // 略
        // ...
        // Use AndroidX test runner
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        // 2) Connect JUnit 5 to the runner
        testInstrumentationRunnerArgument "runnerBuilder", "de.mannodermaus.junit5.AndroidJUnit5Builder"
    }  

    // 3) Java 8 is required, add this even if minSdkVersion is 26 or above
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }  

    // 4) JUnit 5 will bundle in files with identical paths; exclude them
    packagingOptions {
        // ...
        // 略
        // ...
        exclude("META-INF/LICENSE*")
    }
}  

// 5) (Optional) If use ParameterizedTest ArgumentsProvider, this is required for set to recompile with "-jvm-target 1.8"
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
    kotlinOptions {
        jvmTarget = "1.8"
    }
}  

dependencies {
    // ...
    // 略
    // ...
    // 6) Jupiter API
    androidTestImplementation "org.junit.jupiter:junit-jupiter-api:5.4.2"   
    // 7) (Optional) Jupiter Parameters
    androidTestImplementation "org.junit.jupiter:junit-jupiter-params:5.4.2"

    // 8) JUnit 5 instrumentation companion libraries
    androidTestImplementation "de.mannodermaus.junit5:android-test-core:1.0.0"
    androidTestRuntimeOnly "de.mannodermaus.junit5:android-test-runner:1.0.0"

    androidTestImplementation "androidx.test.espresso:espresso-core:3.2.1"
    // ...
    // 略
    // ...
}

app/build.gradleに下記を追記します。

defaultConfig内 :

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArgument "runnerBuilder", "de.mannodermaus.junit5.AndroidJUnit5Builder"

compileOptions内 :

    // 3) Java 8 is required, add this even if minSdkVersion is 26 or above
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

packagingOptions内

    // 4) JUnit 5 will bundle in files with identical paths; exclude them
    packagingOptions {
        // ...
        // 略
        // ...
        exclude("META-INF/LICENSE*")
    }

これを書き忘れるとエラーが起きます。

Optional(自由選択)とありますが、下記も必須

// 5) (Optional) If use ParameterizedTest ArgumentsProvider, this is required for set to recompile with "-jvm-target 1.8"
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
    kotlinOptions {
        jvmTarget = "1.8"
    }
}  

最後にdependenciesに以下を追加

dependencies {
    // ...
    // 略
    // ...
    // 6) Jupiter API
    androidTestImplementation "org.junit.jupiter:junit-jupiter-api:5.4.2"   
    // 7) (Optional) Jupiter Parameters
    androidTestImplementation "org.junit.jupiter:junit-jupiter-params:5.4.2"

    // 8) JUnit 5 instrumentation companion libraries
    androidTestImplementation "de.mannodermaus.junit5:android-test-core:1.0.0"
    androidTestRuntimeOnly "de.mannodermaus.junit5:android-test-runner:1.0.0"

    androidTestImplementation "androidx.test.espresso:espresso-core:3.2.1"
    // ...
    // 略
    // ...
}


3. テストコードの追加

今回はとりあえず、JUnit5が動作することを確認したいので、applicationIdが正しいかどうかを判定するテストを追加します。
これならネットワークにつながっていようがいまいが動作確認ができるためです。

AndroidプロジェクトでUnitテストを追加する

とりあえず、MainActivityを開きます。
その状態で、⌘+shift+Tを押下

Create new test…と出てくるのでクリック。

出てきたダイアログからJUnit5を選択(2019年9月現在のデフォルトはJUnit4)

AndroidプロジェクトでUnitテストを追加する(JUnit5)

Choose Destination Directoryダイアログが出てきます。

Android Studio3.5でUnitテストを追加する

ここでいくつかディレクトリが選択できるようになっています。
今回は一旦デフォルトのandroidTestDebugでもandroidTestでも、どちらでも良いと思います。(ほぼ意味のないテストのため)

テストコードを書く

MainActivityTest.kt

package jp.sub.takelab.twitmorus.activity

import de.mannodermaus.junit5.ActivityScenarioExtension
import org.junit.Assert.*
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.RegisterExtension

/***
 * JUnit5 でシナリオテスト
 * @see https://swet.dena.com/entry/2019/06/12/170000
 *
 */
class MainActivityTest {

    @JvmField
    @RegisterExtension
    val scenarioExtension = ActivityScenarioExtension.launch<MainActivity>()

    // 通信状態に関わらずapplicationIdが取得できること
    @Test
    fun getApplicationIdTest() {
        val scenario = scenarioExtension.scenario

        scenario.onActivity {
            assertEquals("jp.sub.takelab.twitmorus", it.applicationContext.packageName)
        }
    }
}

applicationIdが”jp.sub.takelab.twitmorus”と同じであるかを見る簡単なテストです。

de.mannodermaus.junit5.ActivityScenarioExtensionを使うと、AndroidのActivityライフサイクルに則ったテストが行えます。

動作確認

Android上でJUnit5を動かす

上のメニュー(?)からAll Testを選択して実行するとエミュレータが立ち上がります。(実機でも動作します)
エミュレータか実機がないとこれらのUIテストは動かない点に注意です。Android上で動くテストをするので。

JUnit5でユニットテストを回してみる on Android

エミュレータ(or 実機上)でテストが実行され、想定した結果になっていればOKです。

JUnit5 × AndroidX によるUIテストを導入してみて

UIにおけるシナリオテストが書ける点が良いですね。
ここではActivityScenarioExtensionについて詳しく述べませんが、ActivityExtentionScenario#launchメソッドでActivity#onCreate時のテストができます。

例えば初期値や、onClickのテストなども行えそうです。

これをInstrumentation Testというそうです。
Instrumentation TestはContextやSharedPreferenceといったAndroid上での動作するものをテストすることを言うみたいです。

へぇー、って感じです。

ただ、Web上に転がっている資料が古かったり、なかなかJUnit5の日本語資料がなかったので最初はユニットテストを動作させるまで多少時間がかかりました。JUnit4の資料は結構見つかるのですが・・・。

参考資料


test/とandroidTest/のディレクトリの違い

先程の画像にあったtest/ ディレクトリとandroidTest/ ディレクトリの違いについても触れてみます。

andrdoidTest/ ディレクトリ

androidフレームワークに依存するテストを格納する場所。
例えば、View.onClickなどAndroid特有のメソッドを使うクラスのテストなどを格納します。

test/ ディレクトリ

androidフレームワークに依存しないテストを格納する場所。
純粋なJavaファイルやKotlinファイルなどAndroidの動作に関係のない自作クラスのテストを追加します。

androidTestDebug/ と testDebug/

こちらはdebugビルド時に実行されるテストのみを格納します。
ビルドフレーバーでdebugビルドの他、releaseビルド、stagingビルドなどにも対応するためにこのようなディレクトリが選択できるようになっているのだと思います。
デバッグビルドのテストで本番環境APIなどを叩かないようにするための使い分けができます。

参考資料

次回はもっと突っ込んだテストを追加してみたいと思います。
以上、自作Androidアプリでユニットテストが動くまでの備忘録でした。

コメント

タイトルとURLをコピーしました