こんにちは。SWETチームの@zhailujiaです。 今回はAndroidX x Junit5を使ったUIテストの書き方を紹介して行きたいと思います。
背景
- JUnit 5はJUnit 4と比べて複数な新機能があって使いたいところですが、現時点GoogleはAndroidのJUnit 5テストをまだ公式対応していません。
- android-junit5は、AndoridプロジェクトでもJUnit5の使用を可能にするサードパーティ製のGradle Pluginです。
- 今年4月に、android-junit5から新しいInstrumentationサポート用のInstrumentation 1.0.0Libraryをリリースしました。
- これによりAndroidXのActivityScenarioなどのAPIとJunit5を組み合わせたUIテストが遂に書けるになりました。
システム環境
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
今回動作確認したバージョン
- 開発環境
- AndroidStudio 3.4.1
- Android Gradle Plugin 3.2.1
- Gradle 4.10.1
- Java SE 1.8.0_181
- 検証端末
- Android 9.0/API 28
android-junit5プラグインの導入
- 下記のサンプルはInstrumentation Tests on JUnit 5に必要最小限の設定を記述しています。
- Android Instrumentation Test on JUnit 4、Local Unit Test on JUnit 5も同一プロジェクトに設定して共存できますが、今回は割愛します。
1. プロジェクトルートのbuild.gradle
buildscript { dependencies { // 1) Add android-junit5 plugin to project classpath "de.mannodermaus.gradle.plugins:android-junit5:1.4.2.0" } }
2. モジュールの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.1.1" }
Instrumentation Testテストを書こう
ProjectソースはAndroid-Junit5/Instrumeny/Sample/から引用しています、ご参照ください。
AndroidX ActivityScenario on JUnit 5
1. ActivityScenarioExtensionから実現できるJUint5 Instrumentationテスト
- android-junit5はActivityScenarioExtensionを提供することで、ActivityScenario APIを使用可能になりました。
- ActivityScenarioExtensionはAndroidX x JUnit4のActivityScenarioRuleのJUnit 5版です。
- Android Test x JUnit4のActivityTestRuleは廃止予定なので、そのJUnit 5版@ActivityTestも廃止になりました。
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText import de.mannodermaus.junit5.ActivityScenarioExtension import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.RegisterExtension /* Android Oreo/8.0/API 26 以下はスキップされる */ class ExampleInstrumentedTest { /* Instrumentation Testのために、Activityを起動します @JvmField annotation - kotlinで書いているなら必要 @RegisterExtension annotation - ここが最重要、ActivityScenarioのExtension - JUnit 4の@RuleのJUnit 5版 ActivityScenarioExtension.launch<activityClass> - ActivityScenarioRuleの代わりに使う - activityClassを起動します */ @JvmField @RegisterExtension val scenarioExtension = ActivityScenarioExtension.launch<ActivityOne>() @Test fun testExample() { onView(withId(R.id.textView)).check(matches(withText("0"))) } }
2. ActivityScenarioExtensionとActivityScenarioの関係
- ActivityScenarioExtensionからActivityScenarioを取得できる
@Test fun testExampleUseScenario() { // use scenarioExtension to get ActivityScenario val scenario = scenarioExtension.scenario // then get Activity just same as JUnit 4 scenario.onActivity { activity -> assertEquals(0, activity.getClickCount()) } }
3. Use ActivityScenario with "JUnit 5 Parameter Resolution" feature
- ActivityScenarioExtensionはJUnit5のParameterResolverを継承しているので、Parameter Resolution機能に対応している。
- そのため、ActivityScenarioは、直接テストケースのパラメータとして使うことができる。
// A scenario can be passed into a test method directly, when using the ActivityScenarioExtension @Test fun testExampleWithParameter(scenario: ActivityScenario<ActivityOne>) { scenario.onActivity { assertEquals(0, it.getClickCount()) } }
JUnit 5をもっと使ってみよう
Android JUnit 4とJUnit 5のAnnotationマッピング
JUnit 4 | JUnit 5 |
---|---|
@org.junit.Test |
@org.junit.jupiter.api.Test |
@RunWith |
deprecated |
@Ignore |
@Disabled |
n/a | @DisplayName |
n/a | @Nested |
n/a | @ParameterizedTest + <Source> |
n/a | @RepeatedTest(int) |
n/a | @TestInstance |
Assert.assertXXX | Assertions.assertXXX |
これらのうち、JUnit5で特徴的な、次の2つの機能について、具体的な使い方を紹介します
- テストの構造化に使う
@Nested
と@Display
- パラメトライズドテストに使う
@RepeatedTest
と@ParameterizedTest
1. NestedとDisplayName
- すでにJUnit 5のunit testで馴染まれているNestedとDisplayNameアノテーションはinstrumentation testでも使えます。
- テストの構造化、日本語の表示を実現できます。
@Nested @DisplayName("Test Group") inner class NestedTests { @Test @DisplayName("テストサンプル") fun testExample() { onView(withId(R.id.textView)).check(matches(withText("0"))) } }
テスト実行結果
2. RepeatedTest
- 指定した回数で繰り返しテストを実行できます。
- RepetitionInfoからcurrentRepetition(現在の実行回数)、totalRepetitions(実行すべき総回数)を取得できます。
- RepeatedTestはcustom DisplayNameに対応している。
// repeat this test 3 times // combines a custom display name pattern of each repetition via the name attribute @RepeatedTest(value = 3, name = "{currentRepetition}/{totalRepetitions} clickCount={currentRepetition}, expected=''{currentRepetition}''") fun repeatedTestExample(repetitionInfo: RepetitionInfo) { val count = repetitionInfo.currentRepetition for (i in 0 until count) { onView(withId(R.id.button)).perform(click()) } onView(withId(R.id.textView)).check(matches(withText(count.toString()))) }
テスト実行結果
3. ParameterizedTest
- ParameterizedTestは異なる引数でテストを複数回実行できます。
- テストデータは引数ソースSources of Argumentsアノテーションで宣言する。
- ソースアノテーションは複数種類ありますが、今回は展開せず
@ArgumentsSource
を使う例を挙げます。
- ソースアノテーションは複数種類ありますが、今回は展開せず
- テストメソッドのパラメータとしてデータを受け取ります。
- ParameterizedTestもcustom DisplayNameに対応している(今回は省略)。
// define enum for show action description on test results enum class Action(val rawValue :ViewAction) { Click(click()), DoubleClick(doubleClick()), LongClick(longClick()) } // define custom arguments class as an implementation of ArgumentsProvider // ArgumentsProvider must be declared as either a top-level class or as a static nested class private class ButtonTestArguments : ArgumentsProvider { override fun provideArguments(context: ExtensionContext?): Stream<out Arguments> = Stream.of( Arguments.arguments(Action.Click, 1), Arguments.arguments(Action.DoubleClick, 2), Arguments.arguments(Action.LongClick, 1) ) } @ParameterizedTest // @ArgumentsSource is one of the Sources of Arguments Annotation @ArgumentsSource(ButtonTestArguments::class) // get parameters(action,expected) from the Sources of Arguments named ButtonTestArguments // can use both of parameterized arguments(action, expected) and extensions parameter resolution(scenario) at the same time fun parameterizedTestExample(action: Action , expected: Int, scenario: ActivityScenario<ActivityOne>) { onView(withId(R.id.button)).perform(action.rawValue) scenario.onActivity { assertEquals(expected, it.getClickCount()) } }
テスト実行結果
パラメタライズドテストは以下のような効果が考えられます
- RepeatedTestとParameterizedTestを活用して、重複するテストコード、テストケースを削減できます。
- argumentsを利用してにテストのGiven-When-Thenをペアに設定ことで可読性が上がります。
- Esspressoだけで画面上複数のビューの操作する時、テストコードがダラダラ長くなりがち部分も、arguments化して綺麗にまとまります。
終わりに
- 今回紹介したJUnit 5を使えるAndroid UIテストの書き方は分かりやすいですか? ご興味があれば試してみてください。
- android-junit5はまだリリースされたばかりなので、RepeatedTestとParameterizedTestの動作にはいくつか注意点がありますが、今回は割愛させていただきます。
- 紹介した内容以外にもAndroidX Fragment Test、Android Test OrchestratorやJUnit5 Test Instance Lifecycleなど一歩進んだ使い方もあります。
- 今回はこれで終わりにしようと思いますので、今度チャンスがあればまた一緒にAndroidX x JUnit5のさらなる活用を使ってみましょう。
参考リスト
- https://github.com/mannodermaus/android-junit5/wiki/Instrumentation-Tests-Setup
- https://junit.org/junit5/docs/current/user-guide/
謝辞
- @marcelschnellさんと共同開発者たちが素晴らしいプラグインを開発してくださって深く感謝しています。
- 同じくSWETグループの外山さんと田熊さんからAndroidテストとJUnit 5に関して貴重な意見と経験を教えていただきました。ありがとうございます。