Android EspressoでクイズアプリのUIテストを導入してみた (Windows)

# 概要

本記事はWindowsユーザー向けにAndroid EspressoでクイズアプリのUIテスト導入までの手順を紹介しています。


# 開発環境

Android Studio
https://developer.android.com/studio/install?hl=ja

# 言語
Java

# テスト対象アプリ

今回はCode for fun様のクイズアプリを参照させていただきます。(Java)
https://codeforfun.jp/android-studio-quiz-game-1/


# 自動化フレームワーク

Espresso
https://developer.android.com/training/testing/espresso?hl=ja


# 手順

### 1. Android Studioの[最新版](URL "https://developer.android.com/studio/install?hl=ja")をインストール
 (本記事ではAndroid Studio Flamingo | 2022.2.1を使用)


### 2.[ テスト対象アプリ](URL "タイトル")をコーディング

上記URLの記事を参考にクイズアプリを作成します。



アプリが完成したらビルドして起動してみましょう
![appresult.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3518465/773040c1-733d-0c34-6b3b-30e9cfb064ea.png)


これでテスト対象アプリの準備ができました。

### 3. Espressoのdependencyを導入

> "プロジェクト" > Gradle Scripts > Build.Gradle (module:app)

にdependencyに下記コードを追加
(プロジェクト内検索でdependencyでも検索可能)

    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'

![dependency.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3518465/9d33788e-1dc4-fbd6-f880-6e752f2c2c7a.png)

:::note warn

つまづきPoint
ライブラリのバージョンが異なるとビルドに失敗します。
その場合、バージョンを合わせてください
:::




Gradle Syncが成功すればテスト準備OKです。

### 4. テスト項目を作成

UIテストを導入する前にテスト項目を準備します。
今回のクイズアプリではクリックとアサートだけで確認できるテストを実装してみます。

・Case1：クイズの出題カウントが正しく表示していること
・Case2：すべての回答が選択され、回答結果画面が出力されること

次回応用ケースとして動的な結果を確認する実装をしたいと思います。
・Case3：正解の回答を押したとき、正解のダイアログが表示されること
・Case4：不正解の回答を押したとき、不正解のダイアログが表示され、正解のテキストが表示されること
・Case5：回答結果画面で正解数が正しく出力されていること

### 5. EspressoのUIテストのコーディング

・Uitestのjava.classファイルをパッケージ名(andoidTest)と書かれているフォルダに作成してください
・UitestのコードをJavaで書いていきます。

![UItest.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3518465/3f0c0662-cf0c-5f7e-35ab-188f64555e43.png)


基本的にアプリのUIテストは動作の命令とアサートで実施します

・アプリの動作の命令 (button click、scroll、swipe)
・アプリの要素をアサート (text、URL、element_id、image)


試しにCase1のコードを書いてみましょう

1. まずはテストメソッドを書いていきます 
@testはJUnitのルールなので必ず記載します

        @test
        public void testQuizCount() {
        //この中にテストコードを記載
        }

2. 下記コードをテストメソッドとテストクラスの間に記載していきます
これもJUnitルールで、テスト対象となるアクティビティを定義する必要があります

        @Rule
        public ActivityScenarioRule<MainActivity> activityScenarioRule = new ActivityScenarioRule<>(MainActivity.class);

3. 実際にテストコードを書いていきます。
   アプリ起動時と同時にMainActivityが定義されているので
   MainActivityのviewに表示されているテキストをアサートします。
   Espressoでのテキストのアサートは下記の通りとなります。

        onView(withId(R.id."対象のID")).check(matches(withText("アサートしたいテキスト")));

:::note info
withText(): 指定したテキストを含むViewを検索します。このメソッドは、onView()メソッド内で使用されます。

matches(): ViewMatcherと組み合わせて使用され、指定した条件に一致するかどうかを確認します。テキストを含むか、完全に一致するか、部分一致するかなどの条件を指定することができます。
:::

4 . 次に回答ボタンを押下するコードを記載します。
   Espressoでのボタンやテキスト押下は下記の通りとなります。
   今回はダイアログのOKテキストを押下したいのでテキストでの例を採用していきます

ボタンの例

        onView(withId(R.id."押下したいボタンid")).perform(click());

テキストの例

        onView(withText("押下したいテキスト")).check(matches(isDisplayed()));
        onView(withText("押下したいテキスト")).perform(click());


:::note info
この例では、onView()メソッドにwithId(R.id.button)またはwithText("text")を指定して、R.id.buttonというIDやテキストを持つボタンを探します。そして、perform(click())を使用してそのボタンをクリックします。

このように、withId()メソッドを使用して対象のViewを見つけ、perform()メソッドを使用して特定のアクション（ここではclick()）を実行します。これにより、ボタンをクリックすることができます。
:::


5 . 最後に必要なimport文をいれていきます。
   Android studioでは解決できるimport分は自動でinsertしてくれますが、
   カーソルを合わせればimport文のサジェストがでてくるのでそれを押下します。

サンプルコード

    package com.example.espressouitest;

    
    import androidx.test.espresso.assertion.ViewAssertions;
    import androidx.test.ext.junit.rules.ActivityScenarioRule;
    import org.junit.Rule;
    import org.junit.Test;

    import static androidx.test.espresso.Espresso.onView;
    import static androidx.test.espresso.action.ViewActions.click;
    import static androidx.test.espresso.assertion.ViewAssertions.matches;

    import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
    import static androidx.test.espresso.matcher.ViewMatchers.withId;
    import static androidx.test.espresso.matcher.ViewMatchers.withText;
    import static org.hamcrest.CoreMatchers.containsString;

    public class QuizUItest {

    @Rule
    public ActivityScenarioRule<MainActivity> activityScenarioRule = new ActivityScenarioRule<>(MainActivity.class);

    @Test
    // Case1:クイズの出題カウントが正しく動作していること
    public void testQuizCount() {

        // 出題カウントの"Q1"のテキストをアサートする
        onView(withId(R.id.countLabel)).check(matches(withText("Q1")));
        // 回答ボタン1をクリックして次のアクティビティに遷移する
        onView(withId(R.id.answerBtn1)).perform(click());
        onView(withText("OK")).check(matches(isDisplayed()));
        onView(withText("OK")).perform(click());

        // 出題カウントの"Q2"のテキストをアサートする
        onView(withId(R.id.countLabel)).check(matches(withText("Q2")));
        }
    }



### 6. テスト実行

テストをRunしてみましょう

基本的にはテストの実行はテストメソッドの左のアイコンをクリックすることで実行できます

![TestRun2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3518465/f2ada241-6275-8352-bd8c-6bc0d849a78f.png)



テスト実行画面

![ezgif-4-eaa3ebc4ae.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3518465/87adb2f7-290b-2d81-9cc6-430af5e31d14.gif)


:::note warn
つまづきPoint
テストの実行時にFailすることがあります
ビルド時のFail場合、実装コードが間違っているか環境問題がほとんどなので
エラー内容を確認してデバッグしましょう。
:::


### 7. 実行結果の確認

テストが期待値通りに動作した場合、
Runタブで画像の様な出力になります。

![resutl2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3518465/77ff8727-41bd-8b35-1b40-115e895b5273.png)

ここで要注意なのはアサート文を入れないで実施した場合や意図しない要素でアサートして合格の出力になることがあります。
期待値のテキストや要素が正しく一致しているパターンと、異なる期待値をいれるパターンのどちらのテストも実施することをお勧めします。

:::note warn
つまづきPoint

テスト実行後のFailの場合、期待値が異なっているかテスト中アプリになにかしら問題があります。
その場合はテスト実施内容をみながらデバッグしましょう。
(例：テスト中アプリがCrashした、テストの要素が見つからない、テストの期待値が異なっている)
:::



### 8. 複数のテストケースの実装

2つ目のケースを実装していきます。
Case2：すべての回答が選択され、回答結果画面が出力されること

1. Case1と同じくテストクラスの中にテストメソッドを書いていきます

        @Test
        public void testSelectAllAnswer() {
        //この中にテストコードを記載
        }

2. MainActivityに表示されている各問の回答ボタンを選択していくステップを書きます
   その際都度クイズカウントが正しく表示されていることも確認していきます

        // 回答ボタン1をクリックして次のアクティビティに遷移する
        onView(withId(R.id.answerBtn1)).perform(click());
        onView(withText("OK")).check(matches(isDisplayed()));
        onView(withText("OK")).perform(click());

        // 出題カウントの"Q2"のテキストをアサートする
        onView(withId(R.id.countLabel)).check(matches(withText("Q2")));
        // 回答ボタン2をクリックして次のアクティビティに遷移する
        onView(withId(R.id.answerBtn2)).perform(click());
        onView(withText("OK")).check(matches(isDisplayed()));
        onView(withText("OK")).perform(click());

        // 出題カウントの"Q3"のテキストをアサートする
        onView(withId(R.id.countLabel)).check(matches(withText("Q3")));
        // 回答ボタン3をクリックして次のアクティビティに遷移する
        onView(withId(R.id.answerBtn3)).perform(click());
        onView(withText("OK")).check(matches(isDisplayed()));
        onView(withText("OK")).perform(click());

        // 出題カウントの"Q4"のテキストをアサートする
        onView(withId(R.id.countLabel)).check(matches(withText("Q4")));
        // 回答ボタン4をクリックして次のアクティビティに遷移する
        onView(withId(R.id.answerBtn4)).perform(click());
        onView(withText("OK")).check(matches(isDisplayed()));
        onView(withText("OK")).perform(click());


3. 次に結果画面のテキストをアサートします

        // 回答結果画面のテキストをアサートする
        onView(withId(R.id.countLabel)).check(matches(withText("結果")));
        onView(withId(R.id.totalScoreLabel)).check(ViewAssertions.matches(withText(containsString("トータルスコア :"))));
        onView(withId(R.id.back_button)).check(matches(withText("もどる")));


4.　実行してみます
![ezgif-4-525a3e935e.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3518465/8a7f7357-1196-715d-5fac-f7bfc53bc565.gif)
 

ボタンクリックとテキストのアサートだけでCase2のテストが実現できました。


sample code

    @Test
    // Case2：すべての回答が選択され、回答結果画面が出力されること
    public void testSelectAllAnswer() {

        // 回答ボタン1をクリックして次のアクティビティに遷移する
        onView(withId(R.id.answerBtn1)).perform(click());
        onView(withText("OK")).check(matches(isDisplayed()));
        onView(withText("OK")).perform(click());

        // 出題カウントの"Q2"のテキストをアサートする
        onView(withId(R.id.countLabel)).check(matches(withText("Q2")));
        // 回答ボタン2をクリックして次のアクティビティに遷移する
        onView(withId(R.id.answerBtn2)).perform(click());
        onView(withText("OK")).check(matches(isDisplayed()));
        onView(withText("OK")).perform(click());

        // 出題カウントの"Q3"のテキストをアサートする
        onView(withId(R.id.countLabel)).check(matches(withText("Q3")));
        // 回答ボタン3をクリックして次のアクティビティに遷移する
        onView(withId(R.id.answerBtn3)).perform(click());
        onView(withText("OK")).check(matches(isDisplayed()));
        onView(withText("OK")).perform(click());

        // 回答結果画面のテキストをアサートする
        onView(withId(R.id.countLabel)).check(matches(withText("結果")));
        onView(withId(R.id.totalScoreLabel)).check(ViewAssertions.matches(withText(containsString("トータルスコア :"))));
        onView(withId(R.id.back_button)).check(matches(withText("もどる")));
    }

### 9. 複数のテストケースの実行

１つのテストクラスに複数のテストが存在している場合一括で実行することも可能です。
その場合はClassファイルの横の実行ボタンを押下します。

![タイトルなし2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3518465/f5aa7563-0cda-6df1-19fc-cdc7c88b271b.png)

テスト結果は下記のように表示されます。

![タイトルなし4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3518465/2993b98b-fe1d-6c2c-51e3-b143216f22b6.png)