7
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Phone AppliAdvent Calendar 2020

Day 16

Android KotlinでMockk使ってみた

Last updated at Posted at 2020-12-15

Phone Appli Advent Calender16日目です!
時間もあったし、高品質でボリューミーな記事を求められている気がしないでもないですが、
そんな幻想をぶち殺す意気込み(?)で書きます。

そもそも誰で何してる人なん?

しがないAndroidエンジニア。
Androidのことならなんでもお任せ...と言いたいところですが、日々精進の毎日です。

今回は、実装ばかりでテストを書かずにのうのうと生きてきた自分から生まれ変わる第一歩として、
ユニットテストで使用出来る、Mockkというライブラリを触ってみた備忘録的なことを書こうかなと思います。

Mockkとは?

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

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

導入手順

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

app/build.gradle
depenadencies {
    ...
    testImplementation "io.mockk:mockk:1.10.0"
}

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

実際に使用してみる

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

テスト対象アプリの作成

入力前 入力後
mockTestAppliBefore.png mockTestAppliAfter.png

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

MainActivityPresenter.kt
....
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回づつ問題なく呼び出されているかをテストしています。
実行結果が以下です。
image.png

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

まとめ

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

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

7
0
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
7
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?