2024.12.19
システムの穴を運用でカバーしようとしてミス多発… バグが大量発生、決算が合わない状態から業務効率化を実現するまで
KMMのテストのtips(全1記事)
リンクをコピー
記事をブックマーク
久保出雅俊氏(以下、久保出):今日の発表、connpass上は「expect/actual」の話だったのですが、テストのtipsの話をします。よろしくお願いします。
久保出といいます。ウォンテッドリーのモバイルエンジニアやっています。wantedly.comのIDは、kubodeが私のプロフィールです。
今日のアジェンダですが、KMMのテストについてと、Kotlin/JVMとのテストの違いについてと、あとはその違いを埋めるtipsの紹介をしたいと思います。
KMMのテストについてですが、KMMのテストがなぜ必要かという話です。
KMMは基本的にはUIを持ちません。
では、KMMのテストはどうしたらいいのか。UI上でマニュアルでテストをしようとなると、UIの実装が必要になってきて、しかもAndroidとiOS両方でのテストが必要になってきます。
そうなると、開発のリードタイムが非常に長くなってしまって、生産性がすごく落ちてしまいます。開発生産性高めるためにも、KMM側でテストを書くのがほぼ必須になっています。
KMMのテストのディレクトリの構成ですが、このようなかたちになっています。
KMMの新しいプロジェクトを作ると、commonTest、iosTest、androidTestの3つのディレクトリがあって、commonの中にiOS、Android両方で実行されるテストコードを置きます。
もし、expect/actualなどを使っていて、プラットフォーム固有のコードがあるのであれば、iosTestや、androidTestにプラットフォーム専用のテストを書くというかたちです。
KMMは、コードの共通化をしていくものなので、基本的にはcommonTestにテストを書いていくことになります。
次に、KMMとKotlin/JVMとのテストの違いですが、commonTestに配置するテストコードでは、Kotlin/CommonのAPIしか利用できません。
どういうことかというと、JVM固有の各種APIや、Kotlin/Nativeに対応してないAPIやライブラリは使えないです。
具体的には、JVMのTestRuleや、ほかのアノテーションベースのJVMの機能や、あとはcoroutine-testみたいな、JVMでしか使えないライブラリなどは利用できません。
また、MockK、Kotlinのモックライブラリですが、これはKotlin/Nativeに今は対応していないので、使えないです。
この差を埋めるためのtipsを、ちょっと紹介していきます。
まずは、JUnitであったTestRuleをKMMで実装するという話です。
そもそも、なぜTestRuleを自作しようとしたかというと、KMMでデータベースを使った機能があって、それをテストしたいからです。
通常、データベースを使うテストの場合は、テストを走らせるたびに、データベースの状態をクリーンな状態にしたいのですが、下にあるような、MyTest1みたいな感じで、TestクラスごとにBeforeTest、AfterTestを毎回毎回書くのをやると、何回も何回も書かなくてはいけなくなってしまいます。
これを省略して、TestRuleみたいなかたちにして、楽にしたいというのがきっかけです。
という感じで、このようなTestRuleを実装しました。実装は、こんな感じです。Testのルール自体のインターフェイスを定義して、TestRuleを定義してます。
カスタムのルールを作るには、このTestRuleを実装していきます。
このHasTestRulesというのは、Testクラスに実装します。HasTestRulesは、@BeforeTest、@AfterTestのアノテーションのついたデフォルトの実装を持ってます。
これを実装することで、このTestクラス側に、@BeforeTest、@AfterTestを書かなくても、デフォルト実装がテストの前後で呼ばれるようになります。
実際に自作したデータベースのTestRuleがこんな感じです。
DbTestRuleは、TestRuleを実装していて、BeforeとAfterの記述をしています。
この場合、testSqlDriver()というのが、毎回DBを作り直しているんですよね。テスト前に毎回データベースが削除されて、クリーンな状態になるというかたちになっています。
実際のTestクラスには、このHasTestRulesを実装します。このTestRulesに、使いたいTestRule、今の場合はdbTestRuleを指定します。
こうすると、@BeforeTest、@AfterTestをこのMyTestの上で書かなくても、TestRuleのBefore、Afterがきちんと呼ばれるようになります。
実際に、JUnitライクなTestRuleというのが、これで実現できます。
テストをクリーンにする実装が、ルールを使うことでまとめられて、テストを書くのがすごく簡単になりました。
次に、Mockについてです。KMMにおいて、現状MockKのようなDSLでいい感じにMockを書くライブラリは、私が知る限りはないです。MockKにはFeatureリクエストが飛んでいるのですが、進んでいない感じです。
なので、Mockを使ってテストしたいとなった場合は、自分で書くしかありません。
ということで、どうやってMockを書いたらいいかを話します。
まずは、Mockを書く前にテスタブルな設計にしないと、そもそもMock化することができません。基本的な話ですが、抽象に依存して、具象に依存しないようにしましょう。
抽象というと、Kotlinでいうinterface、具象というと、class、object、global functionみたいなやつです。
具体的な例を出して進めていきます。
APIから取得したUserを返す、UserRepositoryというのがあって、これをテストしたい場合です。
今書かれてる実装は、このUserRepository create()の中で、ApiClientという具象クラスに依存している状況です。
これをテストしたいなとなった場合、テストは書けません。
これだと、実際にAPI通信とかをしてしまうので、バックエンド含めたテストになりますし、通信にもし失敗したらどうなるかみたいなテストも書きづらいです。
もし、MockKが使えるのであれば、ここでMockを書いて、UserRepositoryのテストはできるかもしれませんが、KMMではMockKが使えないです。
ということで、まずはテストを書ける設計にしていきます。
前述のApiClientを、まずはinterfaceにして、実際のクラスは、Implクラスにして、ApiClientのinterfaceを実装するかたちに変更します。こうすると、抽象と具象を分けることができます。
UserRepositoryは、interface、ApiClientに依存するかたちにして、これを注入します。
こうすると、具象クラスであるImplクラスには依存しなくなります。いわゆる、依存性の注入をやっている状況です。
次に、テストコード用にApiClientInterfaceを実装したMockクラスを作ります。こんな感じで、overrideしたcreateUser()がmock()を呼ぶかたちにします。
このmockプロパティにラムダを入れることで、createUserが呼ばれた時に、ラムダの結果で操作できるようになります。
ここまでやって、ようやくテストが書けるようになります。
このように、Mockクラスのmockプロパティに、モックしたい結果を指定します。apiClient.mock = { User(“test”) }みたいに書きます。
こうして、UserRepositoryにはMockを注入して、create()を呼ぶようにします。
これでcreate()を呼ぶと、内部的にはmockのラムダが呼ばれて、User(“test”)が返ってきます。それから、Mockから期待される結果をアサーションすれば、テストができるようになります。
KMMのMockは、このようなかたちで実現可能です。
Mockを使えば、もしAPIが失敗した時にどういう挙動になるか、みたいなテストも書くことができるようになります。もし例外を投げたらどうなるかというテストがこういうふうに書けるわけです。
最後、まとめです。
KMMのテストは、まだまだライブラリやツールが少ない状況です。なので、なかったら自分で作る精神でいく必要があります。
抽象と具象を分けるというような、テスタブルな設計も重要になってきます。
先ほどのコードの例にあったように、かなりボイラープレートが増えてしまうのですが、ちょっと今はしょうがないかなという感じです。
compiler-pluginとかが、もしいい感じに実装できれば、もっとボイラープラートは減らせると思います。作れたらいいのですが、まだ作れないです(笑)。
発表は以上です。ご清聴ありがとうございました。
関連タグ:
2024.12.20
日本の約10倍がん患者が殺到し、病院はキャパオーバー ジャパンハートが描く医療の未来と、カンボジアに新病院を作る理由
2024.12.19
12万通りの「資格の組み合わせ」の中で厳選された60の項目 532の資格を持つ林雄次氏の新刊『資格のかけ算』の見所
2024.12.16
32歳で成績最下位から1年でトップ営業になれた理由 売るテクニックよりも大事な「あり方」
2023.03.21
民間宇宙開発で高まる「飛行機とロケットの衝突」の危機...どうやって回避する?
PR | 2024.12.20
モンスター化したExcelが、ある日突然崩壊 昭和のガス工事会社を生まれ変わらせた、起死回生のノーコード活用術
2024.12.12
会議で発言しやすくなる「心理的安全性」を高めるには ファシリテーションがうまい人の3つの条件
2024.12.18
「社長以外みんな儲かる給与設計」にした理由 経営者たちが語る、優秀な人材集め・会社を発展させるためのヒント
2024.12.17
面接で「後輩を指導できなさそう」と思われる人の伝え方 歳を重ねるほど重視される経験の「ノウハウ化」
2024.12.13
ファシリテーターは「しゃべらないほうがいい」理由 入山章栄氏が語る、心理的安全性の高い場を作るポイント
2024.12.10
メールのラリー回数でわかる「評価されない人」の特徴 職場での評価を下げる行動5選
Climbers Startup JAPAN EXPO 2024 - 秋 -
2024.11.20 - 2024.11.21
『主体的なキャリア形成』を考える~資格のかけ算について〜
2024.12.07 - 2024.12.07
Startup CTO of the year 2024
2024.11.19 - 2024.11.19
社員の力を引き出す経営戦略〜ひとり一人が自ら成長する組織づくり〜
2024.11.20 - 2024.11.20
「確率思考」で未来を見通す 事業を成功に導く意思決定 ~エビデンス・ベースド・マーケティング思考の調査分析で事業に有効な予測手法とは~
2024.11.05 - 2024.11.05