もなかアイスの試食品

「とりあえずやってみたい」そんな気持ちが先走りすぎて挫折が多い私のメモ書きみたいなものです.

Kotlin + Koinでテストコードを書いてみた

はじめに

ユニットテストの自動化はやった方が良いぞと分かっていても、Androidのコーディングに全然慣れていなかったので手がつかなかった。

プログラミング言語のメージャーバージョンの更新(JavaやらSwift)、ライブラリの更新などで、自分の将来の手間を減らすためにも、ユニットテストの自動化を必要性を感じたので、Androidでのテストの方法を調べてみた。

参考サイト

qiita.com

insert-koin.io

developer.android.com

環境

環境はKotlinと、DIコンテナとしてKoinを使用

SpringBootの影響か、DIコンテナを使わないとテストが書けない身体に・・・(そもそも、DIコンテナ使う前はテスト書いてない)

自分の能力では、DIコンテナを利用しないとモックが作りづらい・・・

Kotlinで使用できるDIコンテナは幾つかあるけど、個人的にKoinが学習コストが低そうだった

JavaではDagger2を使ってたけど、試しにKotlin + Daggar2のコードを書こうとして、面倒臭くなって諦めた)

ライブラリの準備

Koinのライブラリを追加するために、Gradleを修正する

dependencies {
    
    ...(中略)

    // Koin for Android
    implementation 'org.koin:koin-android:1.0.2'
    implementation 'org.koin:koin-test:1.0.2'
}

実装部分の作成

まず、現在時刻を取得するための処理を作る

まずはインターフェースを定義

interface ISystemClock {

    fun currentDateTime(): Calendar

}

「ISystemClock」を実装するクラスを定義する

class SystemClock: ISystemClock {

    override fun currentDateTime(): Calendar {
        return Calendar.getInstance()
    }

}

次に、適当な文字列+時刻の文字列を返す処理を作る

インターフェースを定義

interface IMessenger {

    fun getMessage(): String

}

実装を定義

class Messenger(
    private val iSystemClock: ISystemClock
) : IMessenger {

    override fun getMessage(): String {
        val formatter = SimpleDateFormat("yyyy年MM月dd日 HH時mm分ss秒", Locale.US)
        val dateString = formatter.format(this.iSystemClock.currentDateTime().time)
        return "Hello world!!! ${dateString}"
    }

}

実装は「Hello world!!!」に現在時刻を付け足した文字列を返すだけ

現在時刻の取得は、先に定義したISystemClockの実装から取得するようにしている

テストコードの作成

作成したMessengerの実装が正しく動作するか確認するためのテストを作成する

とりあえず単純なテストを作成してみるが、getMessageメソッドで現在時刻を取得するので失敗する

class MessengerTest {

    private val messenger = Messenger(SystemClock())

    @Test
    fun getMessage() {
        Assert.assertEquals("Hello world!!! 2019年04月01日 10時18分52秒", messenger.getMessage())
    }

}

なので、ISystemClockを実装して決まった時間を返すモッククラスを作成する

class MockSystemClock: ISystemClock {

    override fun currentDateTime(): Calendar {
        val calendar = Calendar.getInstance()
        calendar.set(2019, Calendar.APRIL, 1, 10, 18, 52)
        return calendar
    }

}

そしてテスト時に、モッククラスのインスタンスを使用するように、koinで依存性を定義する

依存性の定義は、適当な場所に「DIModules.kt」というファイルを作成して以下のように記述した

val mockModule: Module = module {
    single { MockSystemClock() as ISystemClock }
    single { Messenger(get()) as IMessenger }
}

依存性を定義したので、テストも定義したものを使用できるように修正する

class MessengerTest: AutoCloseKoinTest() {

    private val iMessenger by inject<IMessenger>()

    @Before
    fun before() {
        startKoin(listOf(mockModule))
    }

    @Test
    fun getMessage() {
        Assert.assertEquals("Hello world!!! 2019年04月01日 10時18分52秒", iMessenger.getMessage())
    }

}

おわりに

Androidアプリに限らず、DIの考え方を利用すると色々な挙動のモックを定義できるので、ユニットテストが作りやすい

iOSの方のテストの自動化についても調べようと思う