Movatterモバイル変換


[0]ホーム

URL:


Hirotaka Kawata, profile picture
Uploaded byHirotaka Kawata
PDF, PPTX6,537 views

Kotest を使って 快適にテストを書こう - KotlinFest 2024

kotest は、 Kotlin ネイティブなテストフレームワークで、Kotlin で書かれたコードをテストするための強力で便利な機能が多く含まれています。Kotlin/JVM では、テストに JUnit を使っている方が多いと思いますが、アサーションライブラリが Java しか考えていなかったり、coroutines, 非同期処理への対応が弱かったりと、もっと Kotlin の機能を最大限に活用して快適にテストを書きたいと思われる場面も多い事でしょう。kotest は、アサーションライブラリだけを JUnit と組み合わせて使うこともできるし、テストフレームワーク全体を kotest へ置き換えることも可能です。このセッションでは、主に今 JUnit を使ってテストを書いている方を対象に、kotest へ移行するとどんな良いことがあるのか、移行のやり方や具体的な実例を紹介します。

Embed presentation

Download as PDF, PPTX
まだ JUnit を使ってるの?Kotest を使って快適にテストを書こうKotlinFest 2024 @hktechno
Hirotaka Kawata - @hktechno大規模な Web サービスの裏側を Server-Side Kotlin で開発● 2024年4月より無職○ 7月からまた働きます (Kotlin 使うよ)● Server-side Kotlin の経験○ メッセンジャーアプリのバックエンド開発■ チャットボット API のリアーキテクチャ■ 国内最大規模のメッセージ配信の裏側を Java -> Kotlin に■ ユニットテスト、API の End-to-end test を Kotest で作成○ フードデリバリーサービス開発■ 新規リアーキテクチャ案件に Kotlin・Kotest を採用して開発Kotlin / Java なバックエンドエンジニア
Kotlin におけるテスト事情何を使ってテスト書いていますか? Assertion に使うライブラリは?JUnit ? hamcrest? AssertJ?ストレス抱えてませんか?もっと Kotlin native な強力なアサーションができたらなぁ。そんなあなたに、テストも Kotlin 風に書きたい!
Kotlin に慣れきった体に Java はつらいよKotlin で JUnit (hamcrest) つらくないですか?明日からはこんな感じにテスト書いてみたくないですか?@Testfun resultBodyClazzTest {assertThat(result!!.body, `is`(instanceOf(Image::class.java)))}context(“result body”) {should(“return image”) {result.shouldNotBeNull().body.shouldBeInstanceOf<Image>()JUnit5 (hamcrest)Kotest
Kotest とは?● Kotlin native なテストライブラリ・フレームワーク○ ScalaTest の影響を強く受けている○ Kotlin Multiplatform 対応● 複数の機能 (後述) が独立、必要な機能だけを導入できる○ JUnit の代わりになるテストフレームワーク全体も提供○ 必ずしも Kotest の Spec を使う必要はない● Kotlin と親和性の高い強力なアサーション○ テストメソッドが拡張関数で提供される○ Kotest DSL による記法も可能● Coroutines や非同期なコードのテストのための機能も充実
Kotest vs JUnit 5● JUnit5○ Java のユニットテストが大前提○ Kotlin 対応はとりあえずある (Java 風味)○ 標準の Assertions や hamcrest は、機能不足● Kotest○ Kotlin native で書きやすく・読みやすく■ Kotlin の型が前提の、強力な Assertion Library■ 拡張関数を多用して、気軽に書ける Assertion■ Lambda を使った柔軟な Assertion○ Test framework まで Kotest を使うとさらに Kotlin っぽく
競合との比較● AssertJ○ Java では一般的な Fluent assertion ライブラリ○ やっぱり、Kotlin 対応が弱い● hamkrest○ あくまで、hamcrest 風味のアサーション○ 正直あまり変わり映えしない、Java 風味● Strikt○ expectThat(subject) から始まる Fluent assertion○ Kotest ほど機能豊富ではなさそう○ Fluent assertion が好きな場合にはありかも?
明日から使える KotestKotest の機能は大きく分けて3つ● Test Framework○ JUnit のような、Kotlin native なテストフレームワーク全体● Assertions Library○ hamcrest や AssertJ の代わりになる Kotlin native なアサーション● Property Testing○ プロパティベーステストのための仕組み全部を使わなくてもいいんです特に、Assertions Library は明日からでも使えます!
Assertions Library
Kotest の Assertions Libraryshould___() というメソッドが基本複数の条件を一度に指定も可能result.shouldBe(expected) // 拡張関数// orresult shouldBe expected // Kotest DSLstr.shouldContain("Kotlin").shouldHaveMinLength(6).shouldHaveMaxLength(10)さよなら assertEquals()Kotlin という文字列を含んだ、6文字以上10文字以下の文字列
とりあえず、IDE で .should してみようめちゃくちゃ楽
Kotest - Assertions Library の導入Gradle の場合、以下を build.gradle.kts に追加JUnit や hamcrest と共存可能、今あるテストを書き換える必要なしtestImplementation('io.kotest:kotest-assertions-core:$version')
明日から使いたくなる強力な Assertions● 一部を除いたのフィールドが同一であることをチェックしたい○ あるフィールドは更新されるがテストには関係ない○ ひとつづつフィールドをチェックするのはとても面倒val userA = User(name = “Kotlin”, ..., updatedAt = null, createdAt = null)// updatedAt と createdAt が DB 上で更新された値が返されるval saved = userRepository.save(userA)saved.shouldBe(userA) // Fail: updatedAt と createdAt が違う
明日から使いたくなる強力な Assertions● .shouldBeEqualToIgnoringFields() を使うと○ 特定のフィールドのみを無視して比較してくれるval userA = User(name = “Kotlin”, ..., updatedAt = null, createdAt = null)// updatedAt と createdAt が DB 上で更新された値が返されるval savedUserA = userRepository.save(userA)// Kotlin の Relection で Property reference を指定可能savedUserA.shouldBeEqualToIgnoringFields(userA, User::updateAt, User::createdAt)
Assertions Library - Inspectorsこんなテスト書いてませんか?もし複数のテストが並列に流れたら?val message = user.getMessages().first()message.type.shouldBe(MessageType.TEXT)message.text.shouldBeStartWith(“Kotlin”)userA.sendMessage(to = userB, ...)userB.getMessages().shouldBeEmpty()本当に first() でいい?メッセージ2つあるかも?メッセージが届いてないことを確認したいが?
Assertions Library - InspectorsInspectors を使うと collection のテストも楽にuser.getMessages().forOne {it.type.shouldBe(MessageType.TEXT)it.text.shouldStartWith("Kotlin")}user.getMessages().forNone {it.type.shouldBe(MessageType.UNKNOWN)}forOne - Collection の中にひとつだけマッチする場合のみ成功forNone - Collection の中にマッチする物がない場合のみ成功
Assertion 結果の分かりやすさ2 elements passed but expected 1The following elements passed:[0] Message(type=TEXT, text=Kotlin)[3] Message(type=TEXT, text=KotlinFest)The following elements failed:[1] Message(type=TEXT, text=Java) => "Java" should startwith "Kotlin" (diverged at index 0)[2] Message(type=TEXT, text=Kotest) => "Kotest" should startwith "Kotlin" (diverged at index 3)forOne - Collection の中にひとつだけマッチする場合のみ成功
非同期なテストの例例えばこんな例、どうやってテストしますか?// 非同期処理: 送信や処理に時間がかかるval messageId = userA.sendMessage(userB, message)// 直後に取得すると失敗するuserB.getMessage(messageId).shouldNotBeEmpty() // => Fail// 非同期処理: 送信や処理に時間がかかる// D は C をブロックしているのでメッセージが届かないことを確認したいval messageId = userC.sendMessage(userD, message)userD.getMessage(messageId).shouldBeEmpty() // 本当にそれでいい?処理に時間がかかる例間違った場合も成功になりがちな例
非同期テスト - Eventually● eventually を使うと、一定時間の間に成功するか判断できる○ coroutines を使った非同期処理を書くと発生しがちなケース○ 繰り返し間隔などの設定も可能// 非同期処理: 送信や処理に時間がかかるval messageId = userA.sendMessage(userB, message)// 10秒間の間に正しい結果に変わったら、成功とみなすeventually(10.seconds) {userB.getMessage(messageId).shouldNotBeEmpty()}
非同期テスト - Continually● continually を使うと、一定時間以上条件が継続することをテスト○ 内部では、一定時間でループして条件をチェックし続ける○ 実装には coroutines を使っている● その他、Retry や Until といったヘルパーもある// 非同期処理: 送信や処理に時間がかかる// D は C をブロックしているのでメッセージが届かないことを確認したいval messageId = userC.sendMessage(userD, message)continually(1.seconds) { // 1秒の間メッセージが受信されない事userD.getMessage(messageId).shouldBeEmpty()}
Clue (手がかり、ヒント)● 何故失敗したか理解困難なテスト結果に遭遇したことは?○ テスト対象フィールド以外の情報がない■ オブジェクトのほかのフィールドが見たい■ 結果の元になったリクエスト情報が見たい○ 壊れやすく安定しないテスト (flaky test) で情報が足りない■ 再現も難しいのに、良く壊れてイライラするval response = user.sendMessage(message)response.isSuccess.shouldBeTrue() // => Fail なぜ?message, response が見たい!
Clue (手がかり、ヒント)● Kotest の Clue を使うと、テスト結果に情報が付加できる○ withClue: 任意の String を Clue として設定○ asClue: 任意のオブジェクトを Clue として設定withClue(“Send: ${message}”) {user.sendMessage(message).asClue {it.isSuccess.shouldBeTrue()}}Send: Message(type=MessageType.TEXT, text=”Kotlin”)SendMessageResponse(isSuccess=false, body=”Unauthorized”)expected:<true> but was:<false>Output
Matcher Modules様々な形式や Kotlin ライブラリ向けの Matcher も用意● JSON - io.kotest:kotest-assertions-json● Ktor - io.kotest.extensions:kotest-assertions-ktor● kotlinx.time - io.kotest.kotest-assertions-kotlinx-timejsonResponse.shouldEqualJson("""{ "a": true }""")response.shouldHaveHeader(“Server”, “Ktor”)instant.shouldBeBefore(Clock.System.now())
Property based testing
Property based testing● ユニットテストちゃんと書けてますか?それテストになってます?○ エッジケース網羅できてます?● 決まった入力パターンだけでテストしてませんか?○ テストパターンを作成するのが面倒くさいval bookA = Book(name = “test book”, category = COMPUTER, pages = 100)shouldNotThrowAny {bookService.createBook(bookA)}決め打ちのパラメータこれ意味ある?
Property based testing● checkAll(): 与えられた Generator を基にテストを評価● Arb: 任意の無作為な (arbitary) 値を生成する GeneratorcheckAll(iterations = 10,Arb.string(), // ランダム文字列Arb.<Category>enum().filter { it != UNKNOWN }, // Enum もArb.int(1..1000) // ランダム整数(エッジケース自動生成)) { name, category, pages ->val bookA = Book(name, category, pages)shouldNotThrowAny {bookService.createBook(bookA)}}使いこなすと便利
Property based testing● Arb はエッジケースを自動生成する (事が多い)○ 例: Arb.string(minSize = 0, maxSize = 100)■ 文字列の長さ (min, max) などが指定された場合、自動的にそのエッジケースの長さが含まれる■ エッジケースのテストを別に作る必要がない!○ 手動でエッジケースを与えることもできる● Arb は Fail したときに自動で Shrinking する (場合がある)○ 文字列長が max では通らない場合、少しづつ減らして確認○ 邪魔になることもあるので、無効化することも可能● Data driven testing もあるが、Property testing がおすすめ
Arb の例 - Property based testing● 標準の Arb○ Arb.string() 文字列○ Arb.int(1..100) 1~100 の整数○ Arb.uuid() UUID○ Arb.enum<EnumType>() Enum の値○ Arb.domain() ドメイン名● Extra arbs - io.kotest.extensions:kotest-property-arbs○ Arb.firstName() 名前○ Arb.wines() ワイン data class(産地・種類・農場)○ Arb.harryPotterCharacter() ハリーポッターの登場人物
Test Framework
Coroutines in JUnit 5● `runTest` 使ってますか?○ え、`runBlocking` 使ってるんですか?○ 使い分けが重要: runTest は delay を無視してくれたり● なんか面倒ですよね?@Testsuspend fun testA() { ... } // Error@Testfun testA() = runTest { ... } // OK
テストケースの名前に満足してますか?● テストケースのメソッド名は長くなりがち○ メソッド名が適当で何やってるかわからない等class UserServiceTest {@Testsuspend fun userCantSendTextMessageToBlockedUserTest() { .. }}文字列で書けたらなぁ
Kotest の Test Framework● 以下の例は StringSpec○ 他にもいくつかのスタイル (Spec) が用意されている○ JUnit 風味の Spec もある (AnnotationSpec)● テキストでテスト名を書くので、テスト結果が分かりやすい!class UserServiceTest : StringSpec({“User can’t send text message to blocked user” {user.sendMessage(...) // coroutines...}}) テストメソッドは標準で suspend funStringSpec:テスト名を文字列で管理
テストケース、構造化してますか?class UserServiceTest {@Nestedclass UserServiceMessageTest {@Testsuspend fun sendMessageTest() { .. }}@Nestedclass UserServiceFriendTest {@Testsuspend fun followTest() { .. }}}JUnit でも @Nested できるけど...
Test Framework - 構造化テスト● ShouldSpec○ should から始まるテスト名を強制できる○ 他にも構造化テストケースに対応した Spec が多数class UserServiceTest : ShouldSpec({context(“send message”) {should(“not send message to blocked user”) {user.sendMessage(...)...}}})何を目的としたテストであるかわかりやすい!
Test Spec - Test Framework● FunSpec● DescribeSpec● ShouldSpec● StringSpec● BehaviorSpec● FreeSpec● WordSpec● FeatureSpec● ExpectSpec● AnnotationSpec沢山ありすぎて、全部紹介できないよ!● おすすめ○ ShouldSpec○ WordSpec○ ExpectSpec○ テストの命名時に、“何を期待しているか”を強制できる
Test Framework の導入● JVM であれば、JUnit runner を使うのが標準○ つまり JUnit の上で kotest の Test Framework が動く○ 既存の JUnit テストと共存が可能● Multiplatform の場合は、kotest-framework-engine を使う● IntelliJ Plugin もあるので入れておきましょうtestImplementation('io.kotest:kotest-runner-junit5:$version')test {useJUnitPlatform()}
Kotest の Test Framework● 柔軟なテストケースの生成○ テストの名前の自由度○ 構造化したテストケース○ 動的なテストケースの生成○ テストケースごとの詳細な設定 (例: timeout, enabledIf)● Coroutines 対応○ test method が標準で suspend fun● 並列テストの設定○ Coroutines で実行するため JUnit より柔軟 & 高速に● など、様々なメリット...
その他 - Test Framework● Mocking○ mockk や mockito-kotlin を使ってください○ ただし、標準では Spec 全体が Singleton のため注意■ mock のリセットが必要■ afterTest { clearMocks(repository) }● @SpringBootTest - io.kotest.extensions:kotest-extensions-spring○ Bean の constructor injection ができる (さよなら lateinit var)@SpringBootTestclass ControllerUnitTest(@MockkBean private val service: SomeService,) : StringSpec({
Kotest を使ってみた感想何だかんだ5年以上 Kotest を業務で使って見た感想 (kotlintest 時代から)● 最近はとても安定している (以前は...)● サービスの網羅的な E2E テスト・外形監視にも利用した○ ユニットテストは違う観点が必要、豊富な機能が役に立つ● チームメンバーからの評価も上々一方で...● Kotlin のバージョンアップで壊れることがある○ kotest はすぐ Kotlin のバージョンに追従する && 新機能を使う○ Kotlin のバージョンアップが必須な事が多い● Test Framework を移行するかは、必要な機能との兼ね合いで判断
まとめ● Kotest は素晴らしいテストフレームワークである○ JUnit からも段階的に置き換えられるので、明日から使える● 強力なアサーションは、テストの信頼性を上げる○ 人間は難しいことに直面すると手抜きしがち○ Kotest の豊富な機能で、脆弱なテストをなくす● Property-based testing を活用すると、さらに堅牢なテストに○ 固定のテストパターンは無意味○ 楽してエッジケースを網羅できる● Kotest の Test Framework を使って、もっと Kotlin らしく○ 謎のメソッド名のテストケースを、わかりやすく

Recommended

PDF
今日からできる!簡単 .NET 高速化 Tips
PDF
GoらしいAPIを求める旅路 (Go Conference 2018 Spring)
PDF
Zynq VIPを利用したテストベンチ
PDF
Zookeeper In Action
PDF
より深く知るオプティマイザとそのチューニング
PDF
Inside vacuum - 第一回PostgreSQLプレ勉強会
PPTX
自動でバグを見つける!プログラム解析と動的バイナリ計装
PDF
PDF
C++ マルチスレッドプログラミング
PDF
本当にわかる Spectre と Meltdown
PDF
Deep Dive async/await in Unity with UniTask(UniRx.Async)
PDF
Vacuum徹底解説
PDF
ネットワークコンフィグ分析ツール Batfish との付き合い方
PDF
3種類のTEE比較(Intel SGX, ARM TrustZone, RISC-V Keystone)
PDF
JJUG CCC 2018 Spring - I-7 (俺が)はじめての Netty
PDF
[CB19] Autopsyで迅速なマルウェアのスキャンとディスク内の簡単調査 by ターナー・功
PDF
お前は PHP の歴史的な理由の数を覚えているのか
PDF
ゼロからはじめるKVM超入門
PDF
AlmaLinux と Rocky Linux の誕生経緯&比較
PPTX
ch 7 POSIX.pptx
PPTX
AHVでみるCVM Autopathの仕組み
PDF
POCO C++ Libraries Intro and Overview
PPTX
NGINX Back to Basics Part 3: Security (Japanese Version)
PPTX
Apache Avro vs Protocol Buffers
PDF
macOSの仮想化技術について ~Virtualization-rs Rust bindings for virtualization.framework ~
PDF
Introduction of AMD Virtual Interrupt Controller
ODP
Sockets and Socket-Buffer
PDF
人生がときめくAPIテスト自動化 with Karate
PDF
ソフトウェア工学2023 11 テスト

More Related Content

PDF
今日からできる!簡単 .NET 高速化 Tips
PDF
GoらしいAPIを求める旅路 (Go Conference 2018 Spring)
PDF
Zynq VIPを利用したテストベンチ
PDF
Zookeeper In Action
PDF
より深く知るオプティマイザとそのチューニング
PDF
Inside vacuum - 第一回PostgreSQLプレ勉強会
PPTX
自動でバグを見つける!プログラム解析と動的バイナリ計装
今日からできる!簡単 .NET 高速化 Tips
GoらしいAPIを求める旅路 (Go Conference 2018 Spring)
Zynq VIPを利用したテストベンチ
Zookeeper In Action
より深く知るオプティマイザとそのチューニング
Inside vacuum - 第一回PostgreSQLプレ勉強会
自動でバグを見つける!プログラム解析と動的バイナリ計装

What's hot

PDF
PDF
C++ マルチスレッドプログラミング
PDF
本当にわかる Spectre と Meltdown
PDF
Deep Dive async/await in Unity with UniTask(UniRx.Async)
PDF
Vacuum徹底解説
PDF
ネットワークコンフィグ分析ツール Batfish との付き合い方
PDF
3種類のTEE比較(Intel SGX, ARM TrustZone, RISC-V Keystone)
PDF
JJUG CCC 2018 Spring - I-7 (俺が)はじめての Netty
PDF
[CB19] Autopsyで迅速なマルウェアのスキャンとディスク内の簡単調査 by ターナー・功
PDF
お前は PHP の歴史的な理由の数を覚えているのか
PDF
ゼロからはじめるKVM超入門
PDF
AlmaLinux と Rocky Linux の誕生経緯&比較
PPTX
ch 7 POSIX.pptx
PPTX
AHVでみるCVM Autopathの仕組み
PDF
POCO C++ Libraries Intro and Overview
PPTX
NGINX Back to Basics Part 3: Security (Japanese Version)
PPTX
Apache Avro vs Protocol Buffers
PDF
macOSの仮想化技術について ~Virtualization-rs Rust bindings for virtualization.framework ~
PDF
Introduction of AMD Virtual Interrupt Controller
ODP
Sockets and Socket-Buffer
C++ マルチスレッドプログラミング
本当にわかる Spectre と Meltdown
Deep Dive async/await in Unity with UniTask(UniRx.Async)
Vacuum徹底解説
ネットワークコンフィグ分析ツール Batfish との付き合い方
3種類のTEE比較(Intel SGX, ARM TrustZone, RISC-V Keystone)
JJUG CCC 2018 Spring - I-7 (俺が)はじめての Netty
[CB19] Autopsyで迅速なマルウェアのスキャンとディスク内の簡単調査 by ターナー・功
お前は PHP の歴史的な理由の数を覚えているのか
ゼロからはじめるKVM超入門
AlmaLinux と Rocky Linux の誕生経緯&比較
ch 7 POSIX.pptx
AHVでみるCVM Autopathの仕組み
POCO C++ Libraries Intro and Overview
NGINX Back to Basics Part 3: Security (Japanese Version)
Apache Avro vs Protocol Buffers
macOSの仮想化技術について ~Virtualization-rs Rust bindings for virtualization.framework ~
Introduction of AMD Virtual Interrupt Controller
Sockets and Socket-Buffer

Similar to Kotest を使って 快適にテストを書こう - KotlinFest 2024

PDF
人生がときめくAPIテスト自動化 with Karate
PDF
ソフトウェア工学2023 11 テスト
PDF
第4回勉強会 単体テストのすすめ
PDF
最近の単体テスト
PDF
xUnit Test Patterns - Chapter19
PDF
Tokyor14 - R言語でユニットテスト
PPTX
Java で書かれたAndroid アプリに Kotlin を適用させていく
PDF
Ruby初級者向けレッスン 第46回 ─── Test::Unit
 
PPTX
Junit4
PDF
Unit test in android
PDF
xUTP Chapter19 (2). Testcase Class
PDF
Akka Unit Testing
PDF
はこだてIKA 第4回勉強会 単体テスト
KEY
テスト初心者Androiderのためのソフトウェアテスト入門
PPTX
JaSST'16 Tokyo モバイルセッション
 
PDF
Qunit再入門 (Version 1.10.0 編)
PDF
JS開発におけるTDDと自動テストツール利用の勘所
PDF
Android Studio開発講座
PPTX
Kotlin が公式サポートになったので Kotlin の話
PPTX
Kotlin 使いになりました
人生がときめくAPIテスト自動化 with Karate
ソフトウェア工学2023 11 テスト
第4回勉強会 単体テストのすすめ
最近の単体テスト
xUnit Test Patterns - Chapter19
Tokyor14 - R言語でユニットテスト
Java で書かれたAndroid アプリに Kotlin を適用させていく
Ruby初級者向けレッスン 第46回 ─── Test::Unit
 
Junit4
Unit test in android
xUTP Chapter19 (2). Testcase Class
Akka Unit Testing
はこだてIKA 第4回勉強会 単体テスト
テスト初心者Androiderのためのソフトウェアテスト入門
JaSST'16 Tokyo モバイルセッション
 
Qunit再入門 (Version 1.10.0 編)
JS開発におけるTDDと自動テストツール利用の勘所
Android Studio開発講座
Kotlin が公式サポートになったので Kotlin の話
Kotlin 使いになりました

More from Hirotaka Kawata

PDF
バイナリより低レイヤな話 (プロセッサの心を読み解く) - カーネル/VM探検隊@北陸1
PPTX
ゼロから始める自作 CPU 入門
PDF
30日でできない!コンピューター自作入門 - カーネル/VM探検隊@つくば
PDF
サーバーサイド Kotlin を社内で普及させてみた - Server-Side Kotlin Night 2025
PDF
KotlinConf 2018 から見る 最近の Kotlin サーバーサイド事情
PDF
xv6 + mist32 + mruby
PDF
Micro Python で組み込み Python
PDF
自作コンピューターでなんかする - 第八回 カーネル/VM探検隊&懇親会
PDF
seccamp2012 チューター発表
PDF
産学間連携推進室(AC部屋) 2012 成果報告会
ODP
Open Design Computer Project - Tsukuba.pm
ODP
About University of Tsukuba Linux User Group
PDF
Introduction of PyCon JP 2014 in PyCon SG
バイナリより低レイヤな話 (プロセッサの心を読み解く) - カーネル/VM探検隊@北陸1
ゼロから始める自作 CPU 入門
30日でできない!コンピューター自作入門 - カーネル/VM探検隊@つくば
サーバーサイド Kotlin を社内で普及させてみた - Server-Side Kotlin Night 2025
KotlinConf 2018 から見る 最近の Kotlin サーバーサイド事情
xv6 + mist32 + mruby
Micro Python で組み込み Python
自作コンピューターでなんかする - 第八回 カーネル/VM探検隊&懇親会
seccamp2012 チューター発表
産学間連携推進室(AC部屋) 2012 成果報告会
Open Design Computer Project - Tsukuba.pm
About University of Tsukuba Linux User Group
Introduction of PyCon JP 2014 in PyCon SG

Kotest を使って 快適にテストを書こう - KotlinFest 2024

  • 1.
    まだ JUnit を使ってるの?Kotestを使って快適にテストを書こうKotlinFest 2024 @hktechno
  • 2.
    Hirotaka Kawata -@hktechno大規模な Web サービスの裏側を Server-Side Kotlin で開発● 2024年4月より無職○ 7月からまた働きます (Kotlin 使うよ)● Server-side Kotlin の経験○ メッセンジャーアプリのバックエンド開発■ チャットボット API のリアーキテクチャ■ 国内最大規模のメッセージ配信の裏側を Java -> Kotlin に■ ユニットテスト、API の End-to-end test を Kotest で作成○ フードデリバリーサービス開発■ 新規リアーキテクチャ案件に Kotlin・Kotest を採用して開発Kotlin / Java なバックエンドエンジニア
  • 3.
    Kotlin におけるテスト事情何を使ってテスト書いていますか? Assertionに使うライブラリは?JUnit ? hamcrest? AssertJ?ストレス抱えてませんか?もっと Kotlin native な強力なアサーションができたらなぁ。そんなあなたに、テストも Kotlin 風に書きたい!
  • 4.
    Kotlin に慣れきった体に JavaはつらいよKotlin で JUnit (hamcrest) つらくないですか?明日からはこんな感じにテスト書いてみたくないですか?@Testfun resultBodyClazzTest {assertThat(result!!.body, `is`(instanceOf(Image::class.java)))}context(“result body”) {should(“return image”) {result.shouldNotBeNull().body.shouldBeInstanceOf<Image>()JUnit5 (hamcrest)Kotest
  • 5.
    Kotest とは?● Kotlinnative なテストライブラリ・フレームワーク○ ScalaTest の影響を強く受けている○ Kotlin Multiplatform 対応● 複数の機能 (後述) が独立、必要な機能だけを導入できる○ JUnit の代わりになるテストフレームワーク全体も提供○ 必ずしも Kotest の Spec を使う必要はない● Kotlin と親和性の高い強力なアサーション○ テストメソッドが拡張関数で提供される○ Kotest DSL による記法も可能● Coroutines や非同期なコードのテストのための機能も充実
  • 6.
    Kotest vs JUnit5● JUnit5○ Java のユニットテストが大前提○ Kotlin 対応はとりあえずある (Java 風味)○ 標準の Assertions や hamcrest は、機能不足● Kotest○ Kotlin native で書きやすく・読みやすく■ Kotlin の型が前提の、強力な Assertion Library■ 拡張関数を多用して、気軽に書ける Assertion■ Lambda を使った柔軟な Assertion○ Test framework まで Kotest を使うとさらに Kotlin っぽく
  • 7.
    競合との比較● AssertJ○ Javaでは一般的な Fluent assertion ライブラリ○ やっぱり、Kotlin 対応が弱い● hamkrest○ あくまで、hamcrest 風味のアサーション○ 正直あまり変わり映えしない、Java 風味● Strikt○ expectThat(subject) から始まる Fluent assertion○ Kotest ほど機能豊富ではなさそう○ Fluent assertion が好きな場合にはありかも?
  • 8.
    明日から使える KotestKotest の機能は大きく分けて3つ●Test Framework○ JUnit のような、Kotlin native なテストフレームワーク全体● Assertions Library○ hamcrest や AssertJ の代わりになる Kotlin native なアサーション● Property Testing○ プロパティベーステストのための仕組み全部を使わなくてもいいんです特に、Assertions Library は明日からでも使えます!
  • 9.
  • 10.
    Kotest の AssertionsLibraryshould___() というメソッドが基本複数の条件を一度に指定も可能result.shouldBe(expected) // 拡張関数// orresult shouldBe expected // Kotest DSLstr.shouldContain("Kotlin").shouldHaveMinLength(6).shouldHaveMaxLength(10)さよなら assertEquals()Kotlin という文字列を含んだ、6文字以上10文字以下の文字列
  • 11.
    とりあえず、IDE で .shouldしてみようめちゃくちゃ楽
  • 12.
    Kotest - AssertionsLibrary の導入Gradle の場合、以下を build.gradle.kts に追加JUnit や hamcrest と共存可能、今あるテストを書き換える必要なしtestImplementation('io.kotest:kotest-assertions-core:$version')
  • 13.
    明日から使いたくなる強力な Assertions● 一部を除いたのフィールドが同一であることをチェックしたい○あるフィールドは更新されるがテストには関係ない○ ひとつづつフィールドをチェックするのはとても面倒val userA = User(name = “Kotlin”, ..., updatedAt = null, createdAt = null)// updatedAt と createdAt が DB 上で更新された値が返されるval saved = userRepository.save(userA)saved.shouldBe(userA) // Fail: updatedAt と createdAt が違う
  • 14.
    明日から使いたくなる強力な Assertions● .shouldBeEqualToIgnoringFields()を使うと○ 特定のフィールドのみを無視して比較してくれるval userA = User(name = “Kotlin”, ..., updatedAt = null, createdAt = null)// updatedAt と createdAt が DB 上で更新された値が返されるval savedUserA = userRepository.save(userA)// Kotlin の Relection で Property reference を指定可能savedUserA.shouldBeEqualToIgnoringFields(userA, User::updateAt, User::createdAt)
  • 15.
    Assertions Library -Inspectorsこんなテスト書いてませんか?もし複数のテストが並列に流れたら?val message = user.getMessages().first()message.type.shouldBe(MessageType.TEXT)message.text.shouldBeStartWith(“Kotlin”)userA.sendMessage(to = userB, ...)userB.getMessages().shouldBeEmpty()本当に first() でいい?メッセージ2つあるかも?メッセージが届いてないことを確認したいが?
  • 16.
    Assertions Library -InspectorsInspectors を使うと collection のテストも楽にuser.getMessages().forOne {it.type.shouldBe(MessageType.TEXT)it.text.shouldStartWith("Kotlin")}user.getMessages().forNone {it.type.shouldBe(MessageType.UNKNOWN)}forOne - Collection の中にひとつだけマッチする場合のみ成功forNone - Collection の中にマッチする物がない場合のみ成功
  • 17.
    Assertion 結果の分かりやすさ2 elementspassed but expected 1The following elements passed:[0] Message(type=TEXT, text=Kotlin)[3] Message(type=TEXT, text=KotlinFest)The following elements failed:[1] Message(type=TEXT, text=Java) => "Java" should startwith "Kotlin" (diverged at index 0)[2] Message(type=TEXT, text=Kotest) => "Kotest" should startwith "Kotlin" (diverged at index 3)forOne - Collection の中にひとつだけマッチする場合のみ成功
  • 18.
    非同期なテストの例例えばこんな例、どうやってテストしますか?// 非同期処理: 送信や処理に時間がかかるvalmessageId = userA.sendMessage(userB, message)// 直後に取得すると失敗するuserB.getMessage(messageId).shouldNotBeEmpty() // => Fail// 非同期処理: 送信や処理に時間がかかる// D は C をブロックしているのでメッセージが届かないことを確認したいval messageId = userC.sendMessage(userD, message)userD.getMessage(messageId).shouldBeEmpty() // 本当にそれでいい?処理に時間がかかる例間違った場合も成功になりがちな例
  • 19.
    非同期テスト - Eventually●eventually を使うと、一定時間の間に成功するか判断できる○ coroutines を使った非同期処理を書くと発生しがちなケース○ 繰り返し間隔などの設定も可能// 非同期処理: 送信や処理に時間がかかるval messageId = userA.sendMessage(userB, message)// 10秒間の間に正しい結果に変わったら、成功とみなすeventually(10.seconds) {userB.getMessage(messageId).shouldNotBeEmpty()}
  • 20.
    非同期テスト - Continually●continually を使うと、一定時間以上条件が継続することをテスト○ 内部では、一定時間でループして条件をチェックし続ける○ 実装には coroutines を使っている● その他、Retry や Until といったヘルパーもある// 非同期処理: 送信や処理に時間がかかる// D は C をブロックしているのでメッセージが届かないことを確認したいval messageId = userC.sendMessage(userD, message)continually(1.seconds) { // 1秒の間メッセージが受信されない事userD.getMessage(messageId).shouldBeEmpty()}
  • 21.
    Clue (手がかり、ヒント)● 何故失敗したか理解困難なテスト結果に遭遇したことは?○テスト対象フィールド以外の情報がない■ オブジェクトのほかのフィールドが見たい■ 結果の元になったリクエスト情報が見たい○ 壊れやすく安定しないテスト (flaky test) で情報が足りない■ 再現も難しいのに、良く壊れてイライラするval response = user.sendMessage(message)response.isSuccess.shouldBeTrue() // => Fail なぜ?message, response が見たい!
  • 22.
    Clue (手がかり、ヒント)● Kotestの Clue を使うと、テスト結果に情報が付加できる○ withClue: 任意の String を Clue として設定○ asClue: 任意のオブジェクトを Clue として設定withClue(“Send: ${message}”) {user.sendMessage(message).asClue {it.isSuccess.shouldBeTrue()}}Send: Message(type=MessageType.TEXT, text=”Kotlin”)SendMessageResponse(isSuccess=false, body=”Unauthorized”)expected:<true> but was:<false>Output
  • 23.
    Matcher Modules様々な形式や Kotlinライブラリ向けの Matcher も用意● JSON - io.kotest:kotest-assertions-json● Ktor - io.kotest.extensions:kotest-assertions-ktor● kotlinx.time - io.kotest.kotest-assertions-kotlinx-timejsonResponse.shouldEqualJson("""{ "a": true }""")response.shouldHaveHeader(“Server”, “Ktor”)instant.shouldBeBefore(Clock.System.now())
  • 24.
  • 25.
    Property based testing●ユニットテストちゃんと書けてますか?それテストになってます?○ エッジケース網羅できてます?● 決まった入力パターンだけでテストしてませんか?○ テストパターンを作成するのが面倒くさいval bookA = Book(name = “test book”, category = COMPUTER, pages = 100)shouldNotThrowAny {bookService.createBook(bookA)}決め打ちのパラメータこれ意味ある?
  • 26.
    Property based testing●checkAll(): 与えられた Generator を基にテストを評価● Arb: 任意の無作為な (arbitary) 値を生成する GeneratorcheckAll(iterations = 10,Arb.string(), // ランダム文字列Arb.<Category>enum().filter { it != UNKNOWN }, // Enum もArb.int(1..1000) // ランダム整数(エッジケース自動生成)) { name, category, pages ->val bookA = Book(name, category, pages)shouldNotThrowAny {bookService.createBook(bookA)}}使いこなすと便利
  • 27.
    Property based testing●Arb はエッジケースを自動生成する (事が多い)○ 例: Arb.string(minSize = 0, maxSize = 100)■ 文字列の長さ (min, max) などが指定された場合、自動的にそのエッジケースの長さが含まれる■ エッジケースのテストを別に作る必要がない!○ 手動でエッジケースを与えることもできる● Arb は Fail したときに自動で Shrinking する (場合がある)○ 文字列長が max では通らない場合、少しづつ減らして確認○ 邪魔になることもあるので、無効化することも可能● Data driven testing もあるが、Property testing がおすすめ
  • 28.
    Arb の例 -Property based testing● 標準の Arb○ Arb.string() 文字列○ Arb.int(1..100) 1~100 の整数○ Arb.uuid() UUID○ Arb.enum<EnumType>() Enum の値○ Arb.domain() ドメイン名● Extra arbs - io.kotest.extensions:kotest-property-arbs○ Arb.firstName() 名前○ Arb.wines() ワイン data class(産地・種類・農場)○ Arb.harryPotterCharacter() ハリーポッターの登場人物
  • 29.
  • 30.
    Coroutines in JUnit5● `runTest` 使ってますか?○ え、`runBlocking` 使ってるんですか?○ 使い分けが重要: runTest は delay を無視してくれたり● なんか面倒ですよね?@Testsuspend fun testA() { ... } // Error@Testfun testA() = runTest { ... } // OK
  • 31.
  • 32.
    Kotest の TestFramework● 以下の例は StringSpec○ 他にもいくつかのスタイル (Spec) が用意されている○ JUnit 風味の Spec もある (AnnotationSpec)● テキストでテスト名を書くので、テスト結果が分かりやすい!class UserServiceTest : StringSpec({“User can’t send text message to blocked user” {user.sendMessage(...) // coroutines...}}) テストメソッドは標準で suspend funStringSpec:テスト名を文字列で管理
  • 33.
    テストケース、構造化してますか?class UserServiceTest {@NestedclassUserServiceMessageTest {@Testsuspend fun sendMessageTest() { .. }}@Nestedclass UserServiceFriendTest {@Testsuspend fun followTest() { .. }}}JUnit でも @Nested できるけど...
  • 34.
    Test Framework -構造化テスト● ShouldSpec○ should から始まるテスト名を強制できる○ 他にも構造化テストケースに対応した Spec が多数class UserServiceTest : ShouldSpec({context(“send message”) {should(“not send message to blocked user”) {user.sendMessage(...)...}}})何を目的としたテストであるかわかりやすい!
  • 35.
    Test Spec -Test Framework● FunSpec● DescribeSpec● ShouldSpec● StringSpec● BehaviorSpec● FreeSpec● WordSpec● FeatureSpec● ExpectSpec● AnnotationSpec沢山ありすぎて、全部紹介できないよ!● おすすめ○ ShouldSpec○ WordSpec○ ExpectSpec○ テストの命名時に、“何を期待しているか”を強制できる
  • 36.
    Test Framework の導入●JVM であれば、JUnit runner を使うのが標準○ つまり JUnit の上で kotest の Test Framework が動く○ 既存の JUnit テストと共存が可能● Multiplatform の場合は、kotest-framework-engine を使う● IntelliJ Plugin もあるので入れておきましょうtestImplementation('io.kotest:kotest-runner-junit5:$version')test {useJUnitPlatform()}
  • 37.
    Kotest の TestFramework● 柔軟なテストケースの生成○ テストの名前の自由度○ 構造化したテストケース○ 動的なテストケースの生成○ テストケースごとの詳細な設定 (例: timeout, enabledIf)● Coroutines 対応○ test method が標準で suspend fun● 並列テストの設定○ Coroutines で実行するため JUnit より柔軟 & 高速に● など、様々なメリット...
  • 38.
    その他 - TestFramework● Mocking○ mockk や mockito-kotlin を使ってください○ ただし、標準では Spec 全体が Singleton のため注意■ mock のリセットが必要■ afterTest { clearMocks(repository) }● @SpringBootTest - io.kotest.extensions:kotest-extensions-spring○ Bean の constructor injection ができる (さよなら lateinit var)@SpringBootTestclass ControllerUnitTest(@MockkBean private val service: SomeService,) : StringSpec({
  • 39.
    Kotest を使ってみた感想何だかんだ5年以上 Kotestを業務で使って見た感想 (kotlintest 時代から)● 最近はとても安定している (以前は...)● サービスの網羅的な E2E テスト・外形監視にも利用した○ ユニットテストは違う観点が必要、豊富な機能が役に立つ● チームメンバーからの評価も上々一方で...● Kotlin のバージョンアップで壊れることがある○ kotest はすぐ Kotlin のバージョンに追従する && 新機能を使う○ Kotlin のバージョンアップが必須な事が多い● Test Framework を移行するかは、必要な機能との兼ね合いで判断
  • 40.
    まとめ● Kotest は素晴らしいテストフレームワークである○JUnit からも段階的に置き換えられるので、明日から使える● 強力なアサーションは、テストの信頼性を上げる○ 人間は難しいことに直面すると手抜きしがち○ Kotest の豊富な機能で、脆弱なテストをなくす● Property-based testing を活用すると、さらに堅牢なテストに○ 固定のテストパターンは無意味○ 楽してエッジケースを網羅できる● Kotest の Test Framework を使って、もっと Kotlin らしく○ 謎のメソッド名のテストケースを、わかりやすく

[8]ページ先頭

©2009-2025 Movatter.jp