AndroidアプリのUnitTest・UITestの始め方についてまとめます。
RecyclerViewのUITestの方法や、Intentを使って条件分岐する方法も記載しています。
Unit Tests
基本
Android Studioなら、簡単にテストの雛形が作成できます。
テスト対象クラスの上でCommand+Shift+Tし、説明に従って進めていくだけです。
詳しくはこの記事を見てください。
https://qiita.com/ikemura23/items/2e28d19142d36835e398
例えば、以下のようなサービスを選択してテストを作成すると、
interface OSService {
fun detectLanguage(): Lang
}
class DefaultOSService : OSService {
override fun detectLanguage(): Lang {
val deviceLanguage = Locale.getDefault().toString()
return Lang(deviceLanguage) ?: Lang.JAPANESE
}
}
以下のようなテストの雛形ができます。
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.Assert.*
class DefaultOSServiceTest {
@Before
fun setUp() {
}
@After
fun tearDown() {
}
@Test
fun detectLanguage() {
}
}
以下では、AssertJとtruthの2つのライブラリでのテスト方法を紹介します。
AssertJ
testImplementation 'org.assertj:assertj-core:3.10.0'
import org.assertj.core.api.Assertions.assertThat // ←これ
import org.junit.After
import org.junit.Before
import org.junit.Test
class DefaultOSServiceTest {
@Before
fun setUp() {}
@After
fun tearDown() {}
@Test
fun detectLanguage() {
Locale.setDefault(Locale.US)
assertThat(DefaultOSService().detectLanguage()).isEqualTo(Lang.ENGLISH)
}
}
Android Studioのクラスもしくはメソッドの左にあるボタンを押すと、
truth
truthはGoogle製のアサーションライブラリで、AssertJと基本的な使い方は揃えられているようです。つまり、AssertJのアサーション例をほぼそのまま利用できます。
testImplementation "com.google.truth:truth:1.0"
androidTestImplementation 'com.google.truth:truth:1.0' // 両方要る模様?
import com.google.common.truth.Truth.assertThat // ←これ
import org.junit.After
import org.junit.Before
import org.junit.Test
class DefaultOSServiceTest {
@Before
fun setUp() {}
@After
fun tearDown() {}
@Test
fun detectLanguage() {
Locale.setDefault(Locale.US)
assertThat(DefaultOSService().detectLanguage()).isEqualTo(Lang.JAPANESE) // AssertJと同じ!
}
}
truthのほうがだいぶ速いようです。何度かやってみましたが、2倍くらいtruthのほうが速かったです。
UI Tests
Espressoを使ったテスト方法を紹介します。
端末設定
テストをする前に、デバイスの設定を変更しておきます。
テスト環境のセットアップ - Android developers
テストが不安定になるのを避けるため、使用する仮想デバイスまたは物理デバイスでシステム アニメーションをオフにすることを強くおすすめします。デバイスの [設定] > [開発者向けオプション] で、次の 3 つの設定を無効にします。
ウィンドウ アニメスケール
トランジション アニメスケール
Animator 再生時間スケール
依存
Espressoを使います。
https://developer.android.com/training/testing/set-up-project#android-test-dependencies
androidTestImplementation
が以下のような感じになっていればOKです。
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.ext:truth:1.2.0'
androidTestImplementation 'com.google.truth:truth:1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-accessibility:3.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-web:3.2.0'
androidTestImplementation 'androidx.test.espresso.idling:idling-concurrent:3.2.0'
テストコード
androidTest内のExampleInstrumentedTest
というサンプルや以下のドキュメントに倣ってテストを作ります。
https://developer.android.com/training/testing/ui-testing/espresso-testing?hl=ja#kotlin
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.filters.LargeTest
import androidx.test.rule.ActivityTestRule
import androidx.test.runner.AndroidJUnit4
@RunWith(AndroidJUnit4::class)
@LargeTest
class HomeViewTest {
private lateinit var title: String
@get:Rule
var activityRule: ActivityTestRule<MainActivity>
= ActivityTestRule(MainActivity::class.java)
@Before
fun initValidString() {
title = "ホームタイトル"
}
@Test
fun checkTitle() {
onView(withText("アプリのホーム画面です")).check(matches(isDisplayed())) // [1]
onView(withId(R.id.cancel_button)).perform(click()) // [2]
onView(withId(R.id.title_text))
.check(matches(withText(title))) // [3]
}
}
このテストでは、以下のことを実行します。
[1] 画面に該当の文字列(を持つ要素)が表示されているか確認
[2] 画面上の該当IDの要素をクリック
[3] 画面上の該当IDの要素が該当の文字列を持つ(表示している)か確認
あとは▶︎ボタンを押してデバイスを選択するとテストが走ります。
起動には時間がかかりますが、テスト自体は速いです。要素を探す処理も、デフォルトで少し待ってくれながらも、見つかればすぐに次の処理を行ってくれるようです。
RecyclerViewのテスト
RecyclerViewは、要素中に色々入れてリストを表示できます。
これをテストする方法を紹介します。
基本のテスト
デフォルトでは、中身に依存しない以下のようなテストが可能です。
- スクロールする
- ○番目の要素にアクションする
- リストに期待値があるか探す(複雑な期待値の用意が大変)
ドキュメントには、「○番目の要素をクリックした後に表示を確認する」例があります。
https://developer.android.com/training/testing/espresso/lists#recycler-view-list-items
@Test fun scrollToItemBelowFold_checkItsText() {
// First, scroll to the position that needs to be matched and click on it.
onView(ViewMatchers.withId(R.id.recyclerView))
.perform(
RecyclerViewActions.actionOnItemAtPosition(
ITEM_BELOW_THE_FOLD,
click()
)
)
// Match the text in an item below the fold and check that it's displayed.
val itemElementText = "${activityRule.activity.resources
.getString(R.string.item_element_text)} ${ITEM_BELOW_THE_FOLD.toString()}"
onView(withText(itemElementText)).check(matches(isDisplayed()))
}
複雑なテスト
もしさらなる複雑なテストをしたい場合、例えば中身の変化などを知りたいなら、AdapterかRecyclerView用のMatcherを拡張する必要があるでしょう。
基本的にはMatcherを拡張するのがよいようです。
EspressoでRecyclerViewの中身を比較 - Qiita
EspressoでRecyclerViewのチェックを行うカスタムMatcherを作成 - Qiita
Intentのextraを使って条件分岐する
テスト開始前にIntentにextraを設定することで、条件分岐に使えます。
コードの下に引数等が分かりにくいものを記しておきます。
@get:Rule
var activityRule: ActivityTestRule<MainActivity>
= ActivityTestRule(MainActivity::class.java, false, false)
@Before
fun setUp() {
var intent = Intent()
intent.putExtra("UI_TEST", true)
activityRule.launchActivity(intent)
}
if (instance.intent.getBooleanExtra("UI_TEST", false)) {
// "UI_TEST"がtrueのとき
} else {
// それ以外
}
-
ActivityTestRule(activityClass: Class<T>, initialTouchMode: Boolean, launchActivity: Boolean)
launchActivity
にfalse
を渡すとテスト開始時にActivityが自動で起動しなくなるため、独自のIntentを設定できます。 -
ActivityTestRule.launchActivity(startIntent: Intent)
テスト下でActivityを起動します。extraを設定したIntentを渡せば、テスト中にextraを取得できます。 -
Intent.getBooleanExtra(name: String, defaultValue: Boolean): Boolean
Intentに設定したBooleanのextraを取得します。defaultValue
はname
に該当するextraがなかった場合に返す値を渡します。
まとめ
- AssertJやtruthを使ってUnitTestを試しました。
- Espressoを使ってUITestを試しました。