LoginSignup
1
3

More than 3 years have passed since last update.

Espresso Test Recorderの導入

Posted at

はじめに

Android Studioに搭載されているUIテスト記録ツール Espresso Test Recorder を使用してみました。
Espresso Test Recorderはアプリの操作を記録することでEspressoでのテストコードへと自動で変換してくれる便利なツールです。
EspressoのAPIの知識がなくても直感的にテストが作成することが可能です。

以下のような、クリックすると文字が変化するありがちなサンプルコードで実際に確認していきます。
c9e9b99255e358d28ef243b64a79407c.gif

実行

  • Android Studioでテストしたいプロジェクトを開いておく
  • テストアプリが動作する端末もしくはエミュレータを用意しておく

当然ですが、上記2つの準備が必要です。
Android Studioから端末が認識されている状態で、Run -> Record Espresso Testを選択すると記録開始です。
fc63752ce912bb7da036ed259a214d22.png

端末上でアプリが立ち上がり、Android Studio上にRecord Your Testと書かれたダイアログが表示され記録できる状態となります。
86969b96889c936de79b17eae75e8494.png

記録

記録が始まったら、実際にテストしたい手順でアプリを操作します。
ひとつ操作するたびに、Record Your Testダイアログに操作した内容が記録されていきます。

記録時の応答速度が遅いため、ダイアログに操作内容が反映されるのを待ってから次の操作を行うようにしましょう。
間違った操作もそのまま記録されてしまうので注意が必要ですが、後ほどコードを手動で修正すればよいだけなのであまり気にする必要はありません。

アサーション

アサーションを記録するには Record Your Test ダイアログ右下のAdd Assertionボタンを押します。
ボタンを押してしばらく待つと、右半分にテスト対象アプリの画面が表示された状態になります。
以下の操作でアサーションを記録していきます。

  1. スクリーンショット上で、検証したい箇所をクリック
  2. 検証対象のViewが赤枠で囲まれるため、対象が正しいか確認
  3. ダイアログ左下のEdit assertion枠でアサーションを設定
  4. ダイアログ左上に検証手順が記録されていることを確認

d1636376206bcb5f538716a9231c6160.png

Save and Add Anotherボタン
アサーションを記録し、引き続き別のアサーションを記録する

Save Assertionボタン
アサーションを記録し、動作の記録に戻る

アサーションの種類

text is

Edit assertionで指定したViewのテキスト文字列と一致しているか検証

exists

Edit assertionで指定したViewが存在していることを検証

does not exist

Edit assertionで指定したViewが存在していないことを検証

テストの保存

先ほどのダイアログの右下のOKを押すと、テストが自動生成されます。
テストクラス名と言語を聞かれるので入力して保存しましょう。

cec651bae928155772750c279f5941bd.png

生成されたテストコード

「ボタンクリック後、指定した文字列が表示されているかどうか」のテストが自動生成されました。

MainActivityTest.kt
package net.storehouse.nono.espressotestrecordersample


import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.action.ViewActions.click
import android.support.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.matcher.ViewMatchers.*
import android.support.test.filters.LargeTest
import android.support.test.rule.ActivityTestRule
import android.support.test.runner.AndroidJUnit4
import android.view.View
import android.view.ViewGroup
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.Matchers.allOf
import org.hamcrest.TypeSafeMatcher
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@LargeTest
@RunWith(AndroidJUnit4::class)
class MainActivityTest {

    @Rule
    @JvmField
    var mActivityTestRule = ActivityTestRule(MainActivity::class.java)

    @Test
    fun mainActivityTest() {
        val appCompatButton = onView(
            allOf(
                withId(R.id.button), withText("クリックする?"),
                childAtPosition(
                    childAtPosition(
                        withId(android.R.id.content),
                        0
                    ),
                    2
                ),
                isDisplayed()
            )
        )
        appCompatButton.perform(click())

        val textView = onView(
            allOf(
                withId(R.id.main_text), withText("ボタンがクリックされました!"),
                childAtPosition(
                    childAtPosition(
                        withId(android.R.id.content),
                        0
                    ),
                    0
                ),
                isDisplayed()
            )
        )
        textView.check(matches(withText("ボタンがクリックされました!")))

        val appCompatButton2 = onView(
            allOf(
                withId(R.id.button), withText("元に戻す"),
                childAtPosition(
                    childAtPosition(
                        withId(android.R.id.content),
                        0
                    ),
                    2
                ),
                isDisplayed()
            )
        )
        appCompatButton2.perform(click())

        val textView2 = onView(
            allOf(
                withId(R.id.main_text), withText("ボタンをクリックしてね"),
                childAtPosition(
                    childAtPosition(
                        withId(android.R.id.content),
                        0
                    ),
                    0
                ),
                isDisplayed()
            )
        )
        textView2.check(matches(withText("ボタンをクリックしてね")))

        val textView3 = onView(
            allOf(
                withId(R.id.main_text2), withText("押すとこのテキストは消えるよ"),
                childAtPosition(
                    childAtPosition(
                        withId(android.R.id.content),
                        0
                    ),
                    1
                ),
                isDisplayed()
            )
        )
        textView3.check(matches(withText("押すとこのテキストは消えるよ")))
    }

    private fun childAtPosition(
        parentMatcher: Matcher<View>, position: Int
    ): Matcher<View> {

        return object : TypeSafeMatcher<View>() {
            override fun describeTo(description: Description) {
                description.appendText("Child at position $position in parent ")
                parentMatcher.describeTo(description)
            }

            public override fun matchesSafely(view: View): Boolean {
                val parent = view.parent
                return parent is ViewGroup && parentMatcher.matches(parent)
                        && view == parent.getChildAt(position)
            }
        }
    }
}

テスト結果

ちゃんとテストも通りますね。
dbf98fe159755563a990f4e7fbcf2f5b.png

気になったところ

  • does not exist でviewが存在しないことを確認するアサーションを作るとき、スクリーンショットに存在しないViewが選べないため結局手動で作る必要がある…?
  • 余計な操作が記憶され、都度消すのはやはり手間

不自由はあるけれど、シンプルなテストだったり簡単な枠組み作成くらいには使えるかも。

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3