Kotlin + Koinでテストコードを書いてみた
はじめに
ユニットテストの自動化はやった方が良いぞと分かっていても、Androidのコーディングに全然慣れていなかったので手がつかなかった。
プログラミング言語のメージャーバージョンの更新(JavaやらSwift)、ライブラリの更新などで、自分の将来の手間を減らすためにも、ユニットテストの自動化を必要性を感じたので、Androidでのテストの方法を調べてみた。
参考サイト
環境
環境はKotlinと、DIコンテナとしてKoinを使用
SpringBootの影響か、DIコンテナを使わないとテストが書けない身体に・・・(そもそも、DIコンテナ使う前はテスト書いてない)
自分の能力では、DIコンテナを利用しないとモックが作りづらい・・・
Kotlinで使用できるDIコンテナは幾つかあるけど、個人的にKoinが学習コストが低そうだった
(JavaではDagger2を使ってたけど、試しにKotlin + Daggar2のコードを書こうとして、面倒臭くなって諦めた)
- Android Studio 3.4
- Kotlin 1.3.30
- Koin 1.0.2
ライブラリの準備
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の方のテストの自動化についても調べようと思う