Help us understand the problem. What is going on with this article?

【Android】テスト(UnitTest/UITest)事始め

AndroidアプリのUnitTest・UITestの始め方についてまとめます。
RecyclerViewのUITestの方法や、Intentを使って条件分岐する方法も記載しています。

Unit Tests

基本

Android Studioなら、簡単にテストの雛形が作成できます。
テスト対象クラスの上でCommand+Shift+Tし、説明に従って進めていくだけです。
詳しくはこの記事を見てください。
https://qiita.com/ikemura23/items/2e28d19142d36835e398

例えば、以下のようなサービスを選択してテストを作成すると、

OSService.kt
interface OSService {
  fun detectLanguage(): Lang
}

class DefaultOSService : OSService {
  override fun detectLanguage(): Lang {
    val deviceLanguage = Locale.getDefault().toString()

    return Lang(deviceLanguage) ?: Lang.JAPANESE
  }
}

以下のようなテストの雛形ができます。

/app/src/test/java/packagename/DefaultOSServiceTest.kt
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() {
    }
}

以下では、AssertJtruthの2つのライブラリでのテスト方法を紹介します。

AssertJ

build.gradle
    testImplementation 'org.assertj:assertj-core:3.10.0'
DefaultOSServiceTest.kt
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のクラスもしくはメソッドの左にあるボタンを押すと、
d0f1d084-40d3-afae-e691-2ea933af2deb.png

テストが走ります。
e5686ae8-ee04-e639-1301-4f5af2275068.png

truth

truthはGoogle製のアサーションライブラリで、AssertJと基本的な使い方は揃えられているようです。つまり、AssertJのアサーション例をほぼそのまま利用できます。

build.gradle
    testImplementation "com.google.truth:truth:1.0"
    androidTestImplementation 'com.google.truth:truth:1.0' // 両方要る模様?
DefaultOSServiceTest.kt
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と同じ!
    }
}

同様にテストできました。
b560b413-d759-c3d6-6f2e-0809fb3a73ad.png

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です。

build.gradle
    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

HomeViewTest.kt
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の要素が該当の文字列を持つ(表示している)か確認

あとは▶︎ボタンを押してデバイスを選択するとテストが走ります。
image.png

通りました。
image.png

起動には時間がかかりますが、テスト自体は速いです。要素を探す処理も、デフォルトで少し待ってくれながらも、見つかればすぐに次の処理を行ってくれるようです。

RecyclerViewのテスト

RecyclerViewは、要素中に色々入れてリストを表示できます。
これをテストする方法を紹介します。

基本のテスト

デフォルトでは、中身に依存しない以下のようなテストが可能です。
1. スクロールする
2. ○番目の要素にアクションする
3. リストに期待値があるか探す(複雑な期待値の用意が大変)

ドキュメントには、「○番目の要素をクリックした後に表示を確認する」例があります。
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を設定することで、条件分岐に使えます。
コードの下に引数等が分かりにくいものを記しておきます。

HomeViewTest.kt
    @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)
    }
MainActivity.kt
if (instance.intent.getBooleanExtra("UI_TEST", false)) {
    // "UI_TEST"がtrueのとき
} else {
    // それ以外
}
  • ActivityTestRule(activityClass: Class<T>, initialTouchMode: Boolean, launchActivity: Boolean)
    launchActivityfalseを渡すとテスト開始時にActivityが自動で起動しなくなるため、独自のIntentを設定できます。
  • ActivityTestRule.launchActivity(startIntent: Intent)
    テスト下でActivityを起動します。extraを設定したIntentを渡せば、テスト中にextraを取得できます。
  • Intent.getBooleanExtra(name: String, defaultValue: Boolean): Boolean
    Intentに設定したBooleanのextraを取得します。defaultValuenameに該当するextraがなかった場合に返す値を渡します。

まとめ

  • AssertJやtruthを使ってUnitTestを試しました。
  • Espressoを使ってUITestを試しました。
mmmmk
フロントエンドエンジニア見習い
5thfloor
Webシステムやモバイルアプリ開発、インフラ構築から運用監視までを幅広く。受託開発のほか、自社プロダクトとして創作・同人活動をサポートする「drill」「hondel」「ぷちのこ」などのサービスを開発・運営しています。
https://www.5thfloor.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした