AndroidアプリでのUnit Testについての解説

これは何?

AndroidアプリをCircleCIでCIする。」のUnit Testを説明した記事です。 サンプルアプリのUnit Testについて解説していきます。

概要

CIの流れでUnit Testが実行されている箇所

AndroidアプリをCircleCIでCIする。」のCIの流れでは (2) Build で実行されています。

ここで使うコードは以下の通りです。

公式ドキュメントはこれです。

ポイント、条件など

Unit Testについて(AndroidアプリをCircleCIでCIする。)にも書いていますがポイントは以下です。

  • MVP(Model-View-Presenter)のアーキテクチャに対してのUnit Testを実行する
  • JUnitを使ってUnit Testを実行する
  • MockitoでViewをモックする
  • HTTPでのアクセスをモックする
  • テストする場所は以下の図の Point for Unit Testing

アーキテクチャはMVPに限っている訳ではありませんが、Interfaceを定義しているとテストを書くのが楽になることから ここではMVPを採用しています。

テストのシナリオ

アプリの機能としては無駄に実装されたJSONPlaceholder のREST APIに接続をしてJSONを取得し、Viewに反映させる部分の動作に関してテストを実施します。
具体的に説明すると上図の Data Interaction をモックしてJSONを固定し、UI Behavior をモックして 指定されている振る舞いを行うか、を確認します。
よって、今回はこのシナリオを確認する為に以下の2つのテストのケースを用意しました。

  • HTTPレスポンスコード200でJSONを正しく受け取った場合の値の確認とViewに対する動作確認
  • HTTPレスポンスコード500を受け取った場合のViewに対する動作確認

準備

build.gradleに追記、org.mockito.plugins.MockMaker の作成の2つが必要です。

build.gradleに追記

app/build.gradle に以下を追記します。

....
dependencies {
....
    // Unit and UI Test
    testImplementation 'junit:junit:4.12'
    testImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
    testImplementation "com.android.support:support-annotations:${android.defaultConfig.targetSdkVersion}"
    testImplementation 'org.robolectric:robolectric:4.0'
    testImplementation 'com.nhaarman:mockito-kotlin-kt1.1:1.5.0'
    testImplementation 'com.squareup.retrofit2:converter-moshi:2.2.0'
    testImplementation 'com.squareup.retrofit2:retrofit-mock:2.3.0'
    testImplementation 'com.squareup.okhttp3:mockwebserver:3.10.0'
....
}
....

build.gradle に以下が書かれていることが前提です。

....
buildscript {
....
    ext.kotlin_version = '1.3.31'
....
}
....

org.mockito.plugins.MockMaker を作成

app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker を新規で作成して、以下の1行を書き込みます。

mock-maker-inline

Unit Testを書く

> REST APIをモックして出力を固定する

Data Interaction の部分をモックして出力を固定します。具体的にはJSONPlaceholderのREST APIの出力を固定する為に HTTPのクライアントOkHttpをモックします。 扱いやすいように、 Unit Test本体 と、 モック を1つのファイルにまとめちゃっています。

MockServerDispatcherのクラスがそれになります。
ここでは、Resp200Resp400Resp500 の3つのClassを定義することで、以下の用に出力を固定します。

  • Resp200:正常系の場合で、JSONを返す
  • Resp400:異常系で、HTTP Statusの400を返す
  • Resp500:異常系で、HTTP Statusの500を返す

> Unit Testを書く

2つのテストケースのうちの「HTTPレスポンスコード200でJSONを正しく受け取った場合の値の確認とViewに対する動作の確認」 の説明を、以下にインラインで行います。。 以下は、

class MainActivityUnitTest {
    private var mMockTestUtils = UnitTestUtils()

    @Test
    fun sampleUnitDataFetchSuccessTest() {
        // vvv 正常系でJSONを返すよう指定
        mMockTestUtils.mockServerBehaviorSwitcher = {
            MockServerDispatcher().Resp200()
        }
        val expectedResponse = MockServerDispatcher().mockedResponse

        var mMainActivityPresenter = MainActivityPresenter()
        // vvv Viewに対する動作の確認の為にMainActivityViewContract()のClassをモック
        var mMainActivityViewContract = mock<MainActivityViewContract>()

        // vvv PresentorにモックしたMainActivityViewContract()を叩かせるようにセット
        mMainActivityPresenter.setView(mMainActivityViewContract)

        // vvv 非同期での処理を同期で動作させるように変更
        mMockTestUtils.prepareRxForTesting()

        // vvv ここでMockしたサーバを起動する
        mMockTestUtils.startMockServer()

        // vvv 以下の2行で、サンプルアプリの接続先をMockしたサーバに入れ替える
        var mApiConnection = mMockTestUtils.setupMockServer()
        setConnection(mApiConnection)

        // vvv PresenterのMethodを叩いて、動作させる
        mMainActivityPresenter.getJsonSampleResponse()
        // vvv MainActivityViewContractのhandleSuccess()が叩かれれ、
        // 固定したデータ(JSON)の取得ができているかを確認
        argumentCaptor<Array<SinglePostResponse>>().apply {
            verify(mMainActivityViewContract, times(1)).handleSuccess(capture())

            for (i in 0 until expectedResponse.size) {
                val expected = expectedResponse[i]
                val actual = this.firstValue[i]
                mMockTestUtils.assertDataClass(expected, actual)
            }

        }
        // vvv ここでMockしたサーバを停止
        mMockTestUtils.shutdownMockServer()
    }
....
}

実行してみる

実行方法には「コマンドラインから」、また「Android Studioから」の2つがあります。

> コマンドラインから実行

Unit Testの全てを実行する

$ ./gradlew testDebugUnitTest;

指定したClass、MethodのUnit Testを実行る

// Classを指定
// --tests のパラメータとして [Package Name].[Class Name] を指定
$ ./gradlew testDebugUnitTest --tests com.example.uitestsample.MainActivityUnitTest;

// ClassとMethodを指定
// --tests のパラメータとして [Package Name].[Class Name].[Method Name] を指定
$ ./gradlew testDebugUnitTest --tests com.example.uitestsample.MainActivityUnitTest.sampleUnitDataFetchSuccessTest;

> Android Studioから

@Testのアノテーションかを書くとTestとして認識されます。 するとAndroid Studioだと下図の赤丸のように、その左側に ▶︎(再生マーク)が表示されるので、 それをクリックすると実行することが可能です。

おわりに

「Cloneしたらすぐに試せる」を目標に書きましたので、興味を持たれた方は試してみていただえると嬉しいです。
間違ってる!とかありましたらPR、またご指摘ください。

2022年の振り返り

Published on December 31, 2022

Continue reading

カヌレマラソン #8

Published on October 09, 2022

カヌレマラソン #7

Published on September 23, 2022