2024.10.10
将来は卵1パックの価格が2倍に? 多くの日本人が知らない世界の新潮流、「動物福祉」とは
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.11.13
週3日働いて年収2,000万稼ぐ元印刷屋のおじさん 好きなことだけして楽に稼ぐ3つのパターン
2024.11.11
自分の「本質的な才能」が見つかる一番簡単な質問 他者から「すごい」と思われても意外と気づかないのが才能
2024.11.13
“退職者が出た時の会社の対応”を従業員は見ている 離職防止策の前に見つめ直したい、部下との向き合い方
2024.11.12
自分の人生にプラスに働く「イライラ」は才能 自分の強みや才能につながる“良いイライラ”を見分けるポイント
2023.03.21
民間宇宙開発で高まる「飛行機とロケットの衝突」の危機...どうやって回避する?
2024.11.11
気づいたら借金、倒産して身ぐるみを剥がされる経営者 起業に「立派な動機」を求められる恐ろしさ
2024.11.11
「退職代行」を使われた管理職の本音と葛藤 メディアで話題、利用者が右肩上がり…企業が置かれている現状とは
2024.11.18
20名の会社でGoogleの採用を真似するのはもったいない 人手不足の時代における「脱能力主義」のヒント
2024.11.12
先週まで元気だったのに、突然辞める「びっくり退職」 退職代行サービスの影響も?上司と部下の“すれ違い”が起きる原因
2024.11.14
よってたかってハイリスクのビジネスモデルに仕立て上げるステークホルダー 「社会的理由」が求められる時代の起業戦略
2024.11.13
週3日働いて年収2,000万稼ぐ元印刷屋のおじさん 好きなことだけして楽に稼ぐ3つのパターン
2024.11.11
自分の「本質的な才能」が見つかる一番簡単な質問 他者から「すごい」と思われても意外と気づかないのが才能
2024.11.13
“退職者が出た時の会社の対応”を従業員は見ている 離職防止策の前に見つめ直したい、部下との向き合い方
2024.11.12
自分の人生にプラスに働く「イライラ」は才能 自分の強みや才能につながる“良いイライラ”を見分けるポイント
2023.03.21
民間宇宙開発で高まる「飛行機とロケットの衝突」の危機...どうやって回避する?
2024.11.11
気づいたら借金、倒産して身ぐるみを剥がされる経営者 起業に「立派な動機」を求められる恐ろしさ
2024.11.11
「退職代行」を使われた管理職の本音と葛藤 メディアで話題、利用者が右肩上がり…企業が置かれている現状とは
2024.11.18
20名の会社でGoogleの採用を真似するのはもったいない 人手不足の時代における「脱能力主義」のヒント
2024.11.12
先週まで元気だったのに、突然辞める「びっくり退職」 退職代行サービスの影響も?上司と部下の“すれ違い”が起きる原因
2024.11.14
よってたかってハイリスクのビジネスモデルに仕立て上げるステークホルダー 「社会的理由」が求められる時代の起業戦略