PHONE APPLI Engineer blog

エンジニアブログ

Android KotlinでMockK使ってみた

こんにちは。 PHONE APPLIでAndroidアプリ開発を担当している田松と申します。 今回は、実装ばかりでテストを書かずにのうのうと生きてきた自分から生まれ変わる第一歩として、ユニットテストで使用出来る、MockKというライブラリを触ってみた備忘録的なことを書こうかなと思います。

MockKとは?

どこの記事にも書いてあることかもしれませんが、Kotlin用のモックライブラリです。使用したことはないですが、Javaの場合だと、Mockitoになるのでしょうか。ワカリマセン

モックですが、簡単に説明するとテスト対象以外のオブジェクトを差し替えて、どのような振る舞いをするかを決められるオブジェクトのことです。
なので、テスト対象外のオブジェクトが存在する場合に、モックに差し替えて動作内容を定義することで、想定しているテスト条件を簡単に作ることが出来ます。

導入手順

まず、MockKを使用するためにライブラリをAndroid Projectに入れるため、以下をappのbuild.gradleに記述します。
※最新は1.10.3ですが、kotlin1.3.72だと動作しないバグあるようなので1.10.0を使用します。

depenadencies {
    ...
    testImplementation "io.mockk:mockk:1.10.0"
}

MockKを使用するにあたって、この一行で終わりです。簡単ですね。

実際に使用してみる

テストのテの字も知らない私ですが、とりあえず使用してみます。まずテストをするにもテスト対象のアプリがないといけませんよね。ということで超適当なアプリを作ってみました。

テスト対象アプリの作成

入力前 入力後
f:id:KazumasaTamatsu:20201222140141p:plain f:id:KazumasaTamatsu:20201222140225p:plain

入力欄に名前と年齢を入れてボタンを押すと1~100のランダムな年数後の年齢が出るっていう何が楽しいのか分からないアプリです。 なんちゃってMVPで以下のようなPresenterを実装しました。

....
class MainActivityPresenter(
        private val view: MainActivityView,
        private val sharedPreferencesRepository: SharedPreferencesRepository
) {

    // ボタンを押された際に呼ばれる
    fun calculateFuture(name: String, age: Int) {
        saveNamePreferences(name)
        saveAgePreferences(age)

        showDisplayName(name)
        futureAge(age)
    }

    // 未来の年齢算出
    private fun futureAge(age: Int) {
        val addYear = (1..100).random()
        showDisplayFuture(addYear)
        showDisplayAge(age + addYear)
    }

    // View側に表示するべき年数を渡す
    private fun showDisplayFuture(year: Int) {
        view.showDisplayFuture(year)
    }

    // View側に表示するべき名前を渡す
    private fun showDisplayName(name: String) {
        view.showDisplayName(name)
    }

    // View側に表示するべき年齢を渡す
    private fun showDisplayAge(age: Int) {
        view.showDisplayAge(age)
    }

    // アプリ領域に入力した名前を保持
    private fun saveNamePreferences(name: String) {
        sharedPreferencesRepository.setPersonName(name)
    }

    // アプリ領域に入力した年齢を保持
    private fun saveAgePreferences(age: Int) {
        sharedPreferencesRepository.setPersonAge(age)
    }
}

テストを実装

改めて基本的にMockKはユニットテスト時に使用するもので、ユニットテストとはメソッド単体毎に実行するテストです。 今回はPresenterに対してユニットテストを作成することで、 ビジネスロジックのみでAndroid特有のクラスを考慮しなくて良いので、テストが作りやすくなります。

早速テストクラスを以下のように作りました。 今回はpublicメソッドである calculateFuture のテストを作ってみました。

import io.mockk.*
import io.mockk.impl.annotations.MockK
import org.junit.Before
import org.junit.Test

class MainActivityPresenterTest {

    private lateinit var presenterSpy: MainActivityPresenter

    // mock
    @MockK
    lateinit var mainActivityView: MainActivityView

    // mock
    @MockK
    lateinit var sharedPreferencesRepository: SharedPreferencesRepository

    private val testName = "test"

    private val testAge = 10

    @Before
    fun setUp() {
        // アノテーションがついたオブジェクトをイニシャライズ
        MockKAnnotations.init(this, relaxUnitFun = true)
        // spykを使用すると実際のオブジェクトと混ぜることが出来る
        presenterSpy = spyk(MainActivityPresenter(mainActivityView, sharedPreferencesRepository), recordPrivateCalls = true)
    }

    @Test
    fun calculateFutureTest() {
        // それぞれプライベート関数が呼ばれた際に何を返すか定義
        every { presenterSpy["saveNamePreferences"](testName) } returns mockk()
        every { presenterSpy["saveAgePreferences"](testAge) } returns mockk()
        every { presenterSpy["showDisplayName"](testName) } returns mockk()
        every { presenterSpy["futureAge"](testAge) } returns mockk()
        // 実際に関数を呼び出す
        presenterSpy.calculateFuture(testName, testAge)
        // それぞれ関数の中で呼び出されているかチェック
        verify(exactly = 1) {
            presenterSpy["saveNamePreferences"](testName)
            presenterSpy["saveAgePreferences"](testAge)
            presenterSpy["showDisplayName"](testName)
            presenterSpy["futureAge"](testAge)
        }
    }
}

それぞれ @MockK アノテーションを使用し、モックオブジェクトとして作成することを宣言します。
MockKAnnotations.init(this, relaxUnitFun = true)アノテーションが付いてるモックオブジェクトに対して、インジェクションします。 relaxUnitFun = true を引数に渡すことによって全ての関数に対して単純な値を返すモックであることを宣言しています。
presenterSpy = spyk(MainActivityPresenter(mainActivityView, sharedPreferencesRepository), recordPrivateCalls = true) によってテスト対象のクラスオブジェクトを作成します。
recordPrivateCalls = true を引数に渡すことによってプライベート関数をモック化できます。 今回はpublicな関数である calculateFuture を呼び出し、その中で指定された関数が1回づつ問題なく呼び出されているかをテストしています。
実行結果が以下です。 f:id:KazumasaTamatsu:20201222142839p:plain

問題なくテストが通りました!

まとめ

今回はテスト対象の関数内で呼び出すべき関数が実装されているかを確認するテストを実装してみました。
ここからPowerMockとかを使ってプライベート関数もテストで実行できるようにしたりとか、値の返却値が問題なく合っているかのテストを実装していければいいなーなんて思ったり思わなかったり。

テスト...頑張って書けるようになろう...


PHONE APPLIについて

phoneappli.net
phoneappli.net