SlideShare a Scribd company logo
まだ 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) つらくないですか?
明日からはこんな感じにテスト書いてみたくないですか?
@Test
fun 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 が好きな場合にはありかも?
明日から使える Kotest
Kotest の機能は大きく分けて3つ
● Test Framework
○ JUnit のような、Kotlin native なテストフレームワーク全体
● Assertions Library
○ hamcrest や AssertJ の代わりになる Kotlin native なアサーション
● Property Testing
○ プロパティベーステストのための仕組み
全部を使わなくてもいいんです
特に、Assertions Library は明日からでも使えます!
Assertions Library
Kotest の Assertions Library
should___() というメソッドが基本
複数の条件を一度に指定も可能
result.shouldBe(expected) // 拡張関数
// or
result shouldBe expected // Kotest DSL
str.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 - Inspectors
Inspectors を使うと 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 1
The 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 start
with "Kotlin" (diverged at index 0)
[2] Message(type=TEXT, text=Kotest) => "Kotest" should start
with "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-time
jsonResponse.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) 値を生成する Generator
checkAll(
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 を無視してくれたり
● なんか面倒ですよね?
@Test
suspend fun testA() { ... } // Error
@Test
fun testA() = runTest { ... } // OK
テストケースの名前に満足してますか?
● テストケースのメソッド名は長くなりがち
○ メソッド名が適当で何やってるかわからない等
class UserServiceTest {
@Test
suspend 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 fun
StringSpec:
テスト名を文字列で管理
テストケース、構造化してますか?
class UserServiceTest {
@Nested
class UserServiceMessageTest {
@Test
suspend fun sendMessageTest() { .. }
}
@Nested
class UserServiceFriendTest {
@Test
suspend 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)
@SpringBootTest
class 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 らしく
○ 謎のメソッド名のテストケースを、わかりやすく

More Related Content

What's hot (20)

トランザクションの設計と進化
トランザクションの設計と進化トランザクションの設計と進化
トランザクションの設計と進化
Kumazaki Hiroki
 
GraalVMでのFlight Recorderを使ったパフォーマンス解析(JJUG CCC 2023 Spring)
GraalVMでのFlight Recorderを使ったパフォーマンス解析(JJUG CCC 2023 Spring)GraalVMでのFlight Recorderを使ったパフォーマンス解析(JJUG CCC 2023 Spring)
GraalVMでのFlight Recorderを使ったパフォーマンス解析(JJUG CCC 2023 Spring)
NTT DATA Technology & Innovation
 
DockerとKubernetesをかけめぐる
DockerとKubernetesをかけめぐるDockerとKubernetesをかけめぐる
DockerとKubernetesをかけめぐる
Kohei Tokunaga
 
CTF for ビギナーズ バイナリ講習資料
CTF for ビギナーズ バイナリ講習資料CTF for ビギナーズ バイナリ講習資料
CTF for ビギナーズ バイナリ講習資料
SECCON Beginners
 
社会人のための本気の英語勉強法
社会人のための本気の英語勉強法社会人のための本気の英語勉強法
社会人のための本気の英語勉強法
Yoshiaki Ieda
 
私たちがGCPを使い始めた本当の理由
私たちがGCPを使い始めた本当の理由私たちがGCPを使い始めた本当の理由
私たちがGCPを使い始めた本当の理由
gree_tech
 
Javaのログ出力: 道具と考え方
Javaのログ出力: 道具と考え方Javaのログ出力: 道具と考え方
Javaのログ出力: 道具と考え方
Taku Miyakawa
 
Oss貢献超入門
Oss貢献超入門Oss貢献超入門
Oss貢献超入門
Michihito Shigemura
 
C#実装から見るDDD(ドメイン駆動設計)
C#実装から見るDDD(ドメイン駆動設計)C#実装から見るDDD(ドメイン駆動設計)
C#実装から見るDDD(ドメイン駆動設計)
Takuya Kawabe
 
ソフトウェアにおける 複雑さとは何なのか?
ソフトウェアにおける 複雑さとは何なのか?ソフトウェアにおける 複雑さとは何なのか?
ソフトウェアにおける 複雑さとは何なのか?
Yoshitaka Kawashima
 
大規模負荷試験時にやったこと
大規模負荷試験時にやったこと大規模負荷試験時にやったこと
大規模負荷試験時にやったこと
まべ☆てっく運営
 
ネットワークでなぜ遅延が生じるのか
ネットワークでなぜ遅延が生じるのかネットワークでなぜ遅延が生じるのか
ネットワークでなぜ遅延が生じるのか
Jun Kato
 
より速く より運用しやすく 進化し続けるJVM(Java Developers Summit Online 2023 発表資料)
より速く より運用しやすく 進化し続けるJVM(Java Developers Summit Online 2023 発表資料)より速く より運用しやすく 進化し続けるJVM(Java Developers Summit Online 2023 発表資料)
より速く より運用しやすく 進化し続けるJVM(Java Developers Summit Online 2023 発表資料)
NTT DATA Technology & Innovation
 
オープンソースで提供される第二のJVM:OpenJ9 VMとIBM Javaについて
オープンソースで提供される第二のJVM:OpenJ9 VMとIBM Javaについてオープンソースで提供される第二のJVM:OpenJ9 VMとIBM Javaについて
オープンソースで提供される第二のJVM:OpenJ9 VMとIBM Javaについて
Takakiyo Tanaka
 
Coherenceを利用するときに気をつけること #OracleCoherence
Coherenceを利用するときに気をつけること #OracleCoherenceCoherenceを利用するときに気をつけること #OracleCoherence
Coherenceを利用するときに気をつけること #OracleCoherence
Toshiaki Maki
 
GraalVM を普通の Java VM として使う ~クラウドベンチマークなどでの比較~
GraalVM を普通の Java VM として使う ~クラウドベンチマークなどでの比較~GraalVM を普通の Java VM として使う ~クラウドベンチマークなどでの比較~
GraalVM を普通の Java VM として使う ~クラウドベンチマークなどでの比較~
Shinji Takao
 
オススメのJavaログ管理手法 ~コンテナ編~(Open Source Conference 2022 Online/Spring 発表資料)
オススメのJavaログ管理手法 ~コンテナ編~(Open Source Conference 2022 Online/Spring 発表資料)オススメのJavaログ管理手法 ~コンテナ編~(Open Source Conference 2022 Online/Spring 発表資料)
オススメのJavaログ管理手法 ~コンテナ編~(Open Source Conference 2022 Online/Spring 発表資料)
NTT DATA Technology & Innovation
 
アプリ開発者、DB 管理者視点での Cloud Spanner 活用方法 | 第 10 回 Google Cloud INSIDE Games & App...
アプリ開発者、DB 管理者視点での Cloud Spanner 活用方法 | 第 10 回 Google Cloud INSIDE Games & App...アプリ開発者、DB 管理者視点での Cloud Spanner 活用方法 | 第 10 回 Google Cloud INSIDE Games & App...
アプリ開発者、DB 管理者視点での Cloud Spanner 活用方法 | 第 10 回 Google Cloud INSIDE Games & App...
Google Cloud Platform - Japan
 
OpenJDKのコミッタってどんなことしたらなったの?解決してきた技術課題の事例から見えてくる必要な知識と技術(JJUG CCC 2023 Spring)
OpenJDKのコミッタってどんなことしたらなったの?解決してきた技術課題の事例から見えてくる必要な知識と技術(JJUG CCC 2023 Spring)OpenJDKのコミッタってどんなことしたらなったの?解決してきた技術課題の事例から見えてくる必要な知識と技術(JJUG CCC 2023 Spring)
OpenJDKのコミッタってどんなことしたらなったの?解決してきた技術課題の事例から見えてくる必要な知識と技術(JJUG CCC 2023 Spring)
NTT DATA Technology & Innovation
 
トランザクションの設計と進化
トランザクションの設計と進化トランザクションの設計と進化
トランザクションの設計と進化
Kumazaki Hiroki
 
GraalVMでのFlight Recorderを使ったパフォーマンス解析(JJUG CCC 2023 Spring)
GraalVMでのFlight Recorderを使ったパフォーマンス解析(JJUG CCC 2023 Spring)GraalVMでのFlight Recorderを使ったパフォーマンス解析(JJUG CCC 2023 Spring)
GraalVMでのFlight Recorderを使ったパフォーマンス解析(JJUG CCC 2023 Spring)
NTT DATA Technology & Innovation
 
DockerとKubernetesをかけめぐる
DockerとKubernetesをかけめぐるDockerとKubernetesをかけめぐる
DockerとKubernetesをかけめぐる
Kohei Tokunaga
 
CTF for ビギナーズ バイナリ講習資料
CTF for ビギナーズ バイナリ講習資料CTF for ビギナーズ バイナリ講習資料
CTF for ビギナーズ バイナリ講習資料
SECCON Beginners
 
社会人のための本気の英語勉強法
社会人のための本気の英語勉強法社会人のための本気の英語勉強法
社会人のための本気の英語勉強法
Yoshiaki Ieda
 
私たちがGCPを使い始めた本当の理由
私たちがGCPを使い始めた本当の理由私たちがGCPを使い始めた本当の理由
私たちがGCPを使い始めた本当の理由
gree_tech
 
Javaのログ出力: 道具と考え方
Javaのログ出力: 道具と考え方Javaのログ出力: 道具と考え方
Javaのログ出力: 道具と考え方
Taku Miyakawa
 
C#実装から見るDDD(ドメイン駆動設計)
C#実装から見るDDD(ドメイン駆動設計)C#実装から見るDDD(ドメイン駆動設計)
C#実装から見るDDD(ドメイン駆動設計)
Takuya Kawabe
 
ソフトウェアにおける 複雑さとは何なのか?
ソフトウェアにおける 複雑さとは何なのか?ソフトウェアにおける 複雑さとは何なのか?
ソフトウェアにおける 複雑さとは何なのか?
Yoshitaka Kawashima
 
大規模負荷試験時にやったこと
大規模負荷試験時にやったこと大規模負荷試験時にやったこと
大規模負荷試験時にやったこと
まべ☆てっく運営
 
ネットワークでなぜ遅延が生じるのか
ネットワークでなぜ遅延が生じるのかネットワークでなぜ遅延が生じるのか
ネットワークでなぜ遅延が生じるのか
Jun Kato
 
より速く より運用しやすく 進化し続けるJVM(Java Developers Summit Online 2023 発表資料)
より速く より運用しやすく 進化し続けるJVM(Java Developers Summit Online 2023 発表資料)より速く より運用しやすく 進化し続けるJVM(Java Developers Summit Online 2023 発表資料)
より速く より運用しやすく 進化し続けるJVM(Java Developers Summit Online 2023 発表資料)
NTT DATA Technology & Innovation
 
オープンソースで提供される第二のJVM:OpenJ9 VMとIBM Javaについて
オープンソースで提供される第二のJVM:OpenJ9 VMとIBM Javaについてオープンソースで提供される第二のJVM:OpenJ9 VMとIBM Javaについて
オープンソースで提供される第二のJVM:OpenJ9 VMとIBM Javaについて
Takakiyo Tanaka
 
Coherenceを利用するときに気をつけること #OracleCoherence
Coherenceを利用するときに気をつけること #OracleCoherenceCoherenceを利用するときに気をつけること #OracleCoherence
Coherenceを利用するときに気をつけること #OracleCoherence
Toshiaki Maki
 
GraalVM を普通の Java VM として使う ~クラウドベンチマークなどでの比較~
GraalVM を普通の Java VM として使う ~クラウドベンチマークなどでの比較~GraalVM を普通の Java VM として使う ~クラウドベンチマークなどでの比較~
GraalVM を普通の Java VM として使う ~クラウドベンチマークなどでの比較~
Shinji Takao
 
オススメのJavaログ管理手法 ~コンテナ編~(Open Source Conference 2022 Online/Spring 発表資料)
オススメのJavaログ管理手法 ~コンテナ編~(Open Source Conference 2022 Online/Spring 発表資料)オススメのJavaログ管理手法 ~コンテナ編~(Open Source Conference 2022 Online/Spring 発表資料)
オススメのJavaログ管理手法 ~コンテナ編~(Open Source Conference 2022 Online/Spring 発表資料)
NTT DATA Technology & Innovation
 
アプリ開発者、DB 管理者視点での Cloud Spanner 活用方法 | 第 10 回 Google Cloud INSIDE Games & App...
アプリ開発者、DB 管理者視点での Cloud Spanner 活用方法 | 第 10 回 Google Cloud INSIDE Games & App...アプリ開発者、DB 管理者視点での Cloud Spanner 活用方法 | 第 10 回 Google Cloud INSIDE Games & App...
アプリ開発者、DB 管理者視点での Cloud Spanner 活用方法 | 第 10 回 Google Cloud INSIDE Games & App...
Google Cloud Platform - Japan
 
OpenJDKのコミッタってどんなことしたらなったの?解決してきた技術課題の事例から見えてくる必要な知識と技術(JJUG CCC 2023 Spring)
OpenJDKのコミッタってどんなことしたらなったの?解決してきた技術課題の事例から見えてくる必要な知識と技術(JJUG CCC 2023 Spring)OpenJDKのコミッタってどんなことしたらなったの?解決してきた技術課題の事例から見えてくる必要な知識と技術(JJUG CCC 2023 Spring)
OpenJDKのコミッタってどんなことしたらなったの?解決してきた技術課題の事例から見えてくる必要な知識と技術(JJUG CCC 2023 Spring)
NTT DATA Technology & Innovation
 

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

xUTP Chapter19 (2). Testcase Class
xUTP Chapter19 (2). Testcase ClassxUTP Chapter19 (2). Testcase Class
xUTP Chapter19 (2). Testcase Class
Takuto Wada
 
Eclipse を使った java 開発 111126 杉浦
Eclipse を使った java 開発 111126 杉浦Eclipse を使った java 開発 111126 杉浦
Eclipse を使った java 開発 111126 杉浦
urasandesu
 
Spock's world
Spock's worldSpock's world
Spock's world
Takuma Watabiki
 
Toxic comment classification
Toxic comment classificationToxic comment classification
Toxic comment classification
Nasuka Sumino
 
xUnit Test Patterns - Chapter11
xUnit Test Patterns - Chapter11xUnit Test Patterns - Chapter11
xUnit Test Patterns - Chapter11
Takuto Wada
 
Javaチョットデキルへの道〜JavaコアSDKに見る真似したいコード10選〜
Javaチョットデキルへの道〜JavaコアSDKに見る真似したいコード10選〜Javaチョットデキルへの道〜JavaコアSDKに見る真似したいコード10選〜
Javaチョットデキルへの道〜JavaコアSDKに見る真似したいコード10選〜
JustSystems Corporation
 
xUnit Test Patterns - Chapter19
xUnit Test Patterns - Chapter19xUnit Test Patterns - Chapter19
xUnit Test Patterns - Chapter19
Takuto Wada
 
xUTP Chapter26. Dependency Injection
xUTP Chapter26. Dependency InjectionxUTP Chapter26. Dependency Injection
xUTP Chapter26. Dependency Injection
Takuto Wada
 
CLRH_120414_WFTDD
CLRH_120414_WFTDDCLRH_120414_WFTDD
CLRH_120414_WFTDD
Tomoyuki Obi
 
PHPUnit でテスト駆動開発を始めよう
PHPUnit でテスト駆動開発を始めようPHPUnit でテスト駆動開発を始めよう
PHPUnit でテスト駆動開発を始めよう
Yuya Takeyama
 
世の中のPostgreSQLエンジニアのpsql設定(第34回PostgreSQLアンカンファレンス@オンライン 発表資料)
世の中のPostgreSQLエンジニアのpsql設定(第34回PostgreSQLアンカンファレンス@オンライン 発表資料)世の中のPostgreSQLエンジニアのpsql設定(第34回PostgreSQLアンカンファレンス@オンライン 発表資料)
世の中のPostgreSQLエンジニアのpsql設定(第34回PostgreSQLアンカンファレンス@オンライン 発表資料)
NTT DATA Technology & Innovation
 
究極のバッチフレームワーク(予定)
究極のバッチフレームワーク(予定)究極のバッチフレームワーク(予定)
究極のバッチフレームワーク(予定)
fumoto kazuhiro
 
CruiseControl.NET設置
CruiseControl.NET設置CruiseControl.NET設置
CruiseControl.NET設置
Kuniaki Igarashi
 
Akka Unit Testing
Akka Unit TestingAkka Unit Testing
Akka Unit Testing
Masashi (Jangsa) Kawaguchi
 
Introduction to Continuous Test Runner MakeGood
Introduction to Continuous Test Runner MakeGoodIntroduction to Continuous Test Runner MakeGood
Introduction to Continuous Test Runner MakeGood
Atsuhiro Kubo
 
C# から java へのプログラム移植で体験したtddの効果は?
C# から java へのプログラム移植で体験したtddの効果は?C# から java へのプログラム移植で体験したtddの効果は?
C# から java へのプログラム移植で体験したtddの効果は?
Shinichi Hirauchi
 
XunitとMoq 公開用
XunitとMoq 公開用XunitとMoq 公開用
XunitとMoq 公開用
ESM SEC
 
タダで始めるテストファースト入門 ~ C# Express + NUnit
タダで始めるテストファースト入門 ~ C# Express + NUnitタダで始めるテストファースト入門 ~ C# Express + NUnit
タダで始めるテストファースト入門 ~ C# Express + NUnit
Yasuhiko Yamamoto
 
xUTP Chapter19 (2). Testcase Class
xUTP Chapter19 (2). Testcase ClassxUTP Chapter19 (2). Testcase Class
xUTP Chapter19 (2). Testcase Class
Takuto Wada
 
Eclipse を使った java 開発 111126 杉浦
Eclipse を使った java 開発 111126 杉浦Eclipse を使った java 開発 111126 杉浦
Eclipse を使った java 開発 111126 杉浦
urasandesu
 
Toxic comment classification
Toxic comment classificationToxic comment classification
Toxic comment classification
Nasuka Sumino
 
xUnit Test Patterns - Chapter11
xUnit Test Patterns - Chapter11xUnit Test Patterns - Chapter11
xUnit Test Patterns - Chapter11
Takuto Wada
 
Javaチョットデキルへの道〜JavaコアSDKに見る真似したいコード10選〜
Javaチョットデキルへの道〜JavaコアSDKに見る真似したいコード10選〜Javaチョットデキルへの道〜JavaコアSDKに見る真似したいコード10選〜
Javaチョットデキルへの道〜JavaコアSDKに見る真似したいコード10選〜
JustSystems Corporation
 
xUnit Test Patterns - Chapter19
xUnit Test Patterns - Chapter19xUnit Test Patterns - Chapter19
xUnit Test Patterns - Chapter19
Takuto Wada
 
xUTP Chapter26. Dependency Injection
xUTP Chapter26. Dependency InjectionxUTP Chapter26. Dependency Injection
xUTP Chapter26. Dependency Injection
Takuto Wada
 
PHPUnit でテスト駆動開発を始めよう
PHPUnit でテスト駆動開発を始めようPHPUnit でテスト駆動開発を始めよう
PHPUnit でテスト駆動開発を始めよう
Yuya Takeyama
 
世の中のPostgreSQLエンジニアのpsql設定(第34回PostgreSQLアンカンファレンス@オンライン 発表資料)
世の中のPostgreSQLエンジニアのpsql設定(第34回PostgreSQLアンカンファレンス@オンライン 発表資料)世の中のPostgreSQLエンジニアのpsql設定(第34回PostgreSQLアンカンファレンス@オンライン 発表資料)
世の中のPostgreSQLエンジニアのpsql設定(第34回PostgreSQLアンカンファレンス@オンライン 発表資料)
NTT DATA Technology & Innovation
 
究極のバッチフレームワーク(予定)
究極のバッチフレームワーク(予定)究極のバッチフレームワーク(予定)
究極のバッチフレームワーク(予定)
fumoto kazuhiro
 
Introduction to Continuous Test Runner MakeGood
Introduction to Continuous Test Runner MakeGoodIntroduction to Continuous Test Runner MakeGood
Introduction to Continuous Test Runner MakeGood
Atsuhiro Kubo
 
C# から java へのプログラム移植で体験したtddの効果は?
C# から java へのプログラム移植で体験したtddの効果は?C# から java へのプログラム移植で体験したtddの効果は?
C# から java へのプログラム移植で体験したtddの効果は?
Shinichi Hirauchi
 
XunitとMoq 公開用
XunitとMoq 公開用XunitとMoq 公開用
XunitとMoq 公開用
ESM SEC
 
タダで始めるテストファースト入門 ~ C# Express + NUnit
タダで始めるテストファースト入門 ~ C# Express + NUnitタダで始めるテストファースト入門 ~ C# Express + NUnit
タダで始めるテストファースト入門 ~ C# Express + NUnit
Yasuhiko Yamamoto
 

More from Hirotaka Kawata (13)

KotlinConf 2018 から見る 最近の Kotlin サーバーサイド事情
KotlinConf 2018 から見る 最近の Kotlin サーバーサイド事情KotlinConf 2018 から見る 最近の Kotlin サーバーサイド事情
KotlinConf 2018 から見る 最近の Kotlin サーバーサイド事情
Hirotaka Kawata
 
本当にわかる Spectre と Meltdown
本当にわかる Spectre と Meltdown本当にわかる Spectre と Meltdown
本当にわかる Spectre と Meltdown
Hirotaka Kawata
 
ゼロから始める自作 CPU 入門
ゼロから始める自作 CPU 入門ゼロから始める自作 CPU 入門
ゼロから始める自作 CPU 入門
Hirotaka Kawata
 
Micro Python で組み込み Python
Micro Python で組み込み PythonMicro Python で組み込み Python
Micro Python で組み込み Python
Hirotaka Kawata
 
バイナリより低レイヤな話 (プロセッサの心を読み解く) - カーネル/VM探検隊@北陸1
バイナリより低レイヤな話 (プロセッサの心を読み解く) - カーネル/VM探検隊@北陸1バイナリより低レイヤな話 (プロセッサの心を読み解く) - カーネル/VM探検隊@北陸1
バイナリより低レイヤな話 (プロセッサの心を読み解く) - カーネル/VM探検隊@北陸1
Hirotaka Kawata
 
Introduction of PyCon JP 2014 in PyCon SG
Introduction of PyCon JP 2014 in PyCon SGIntroduction of PyCon JP 2014 in PyCon SG
Introduction of PyCon JP 2014 in PyCon SG
Hirotaka Kawata
 
自作コンピューターでなんかする - 第八回 カーネル/VM探検隊&懇親会
自作コンピューターでなんかする - 第八回 カーネル/VM探検隊&懇親会自作コンピューターでなんかする - 第八回 カーネル/VM探検隊&懇親会
自作コンピューターでなんかする - 第八回 カーネル/VM探検隊&懇親会
Hirotaka Kawata
 
産学間連携推進室(AC部屋) 2012 成果報告会
産学間連携推進室(AC部屋) 2012 成果報告会産学間連携推進室(AC部屋) 2012 成果報告会
産学間連携推進室(AC部屋) 2012 成果報告会
Hirotaka Kawata
 
30日でできない!コンピューター自作入門 - カーネル/VM探検隊@つくば
30日でできない!コンピューター自作入門 - カーネル/VM探検隊@つくば30日でできない!コンピューター自作入門 - カーネル/VM探検隊@つくば
30日でできない!コンピューター自作入門 - カーネル/VM探検隊@つくば
Hirotaka Kawata
 
seccamp2012 チューター発表
seccamp2012 チューター発表seccamp2012 チューター発表
seccamp2012 チューター発表
Hirotaka Kawata
 
Open Design Computer Project - Tsukuba.pm
Open Design Computer Project - Tsukuba.pmOpen Design Computer Project - Tsukuba.pm
Open Design Computer Project - Tsukuba.pm
Hirotaka Kawata
 
About University of Tsukuba Linux User Group
About University of Tsukuba Linux User GroupAbout University of Tsukuba Linux User Group
About University of Tsukuba Linux User Group
Hirotaka Kawata
 
KotlinConf 2018 から見る 最近の Kotlin サーバーサイド事情
KotlinConf 2018 から見る 最近の Kotlin サーバーサイド事情KotlinConf 2018 から見る 最近の Kotlin サーバーサイド事情
KotlinConf 2018 から見る 最近の Kotlin サーバーサイド事情
Hirotaka Kawata
 
本当にわかる Spectre と Meltdown
本当にわかる Spectre と Meltdown本当にわかる Spectre と Meltdown
本当にわかる Spectre と Meltdown
Hirotaka Kawata
 
ゼロから始める自作 CPU 入門
ゼロから始める自作 CPU 入門ゼロから始める自作 CPU 入門
ゼロから始める自作 CPU 入門
Hirotaka Kawata
 
Micro Python で組み込み Python
Micro Python で組み込み PythonMicro Python で組み込み Python
Micro Python で組み込み Python
Hirotaka Kawata
 
バイナリより低レイヤな話 (プロセッサの心を読み解く) - カーネル/VM探検隊@北陸1
バイナリより低レイヤな話 (プロセッサの心を読み解く) - カーネル/VM探検隊@北陸1バイナリより低レイヤな話 (プロセッサの心を読み解く) - カーネル/VM探検隊@北陸1
バイナリより低レイヤな話 (プロセッサの心を読み解く) - カーネル/VM探検隊@北陸1
Hirotaka Kawata
 
Introduction of PyCon JP 2014 in PyCon SG
Introduction of PyCon JP 2014 in PyCon SGIntroduction of PyCon JP 2014 in PyCon SG
Introduction of PyCon JP 2014 in PyCon SG
Hirotaka Kawata
 
自作コンピューターでなんかする - 第八回 カーネル/VM探検隊&懇親会
自作コンピューターでなんかする - 第八回 カーネル/VM探検隊&懇親会自作コンピューターでなんかする - 第八回 カーネル/VM探検隊&懇親会
自作コンピューターでなんかする - 第八回 カーネル/VM探検隊&懇親会
Hirotaka Kawata
 
産学間連携推進室(AC部屋) 2012 成果報告会
産学間連携推進室(AC部屋) 2012 成果報告会産学間連携推進室(AC部屋) 2012 成果報告会
産学間連携推進室(AC部屋) 2012 成果報告会
Hirotaka Kawata
 
30日でできない!コンピューター自作入門 - カーネル/VM探検隊@つくば
30日でできない!コンピューター自作入門 - カーネル/VM探検隊@つくば30日でできない!コンピューター自作入門 - カーネル/VM探検隊@つくば
30日でできない!コンピューター自作入門 - カーネル/VM探検隊@つくば
Hirotaka Kawata
 
seccamp2012 チューター発表
seccamp2012 チューター発表seccamp2012 チューター発表
seccamp2012 チューター発表
Hirotaka Kawata
 
Open Design Computer Project - Tsukuba.pm
Open Design Computer Project - Tsukuba.pmOpen Design Computer Project - Tsukuba.pm
Open Design Computer Project - Tsukuba.pm
Hirotaka Kawata
 
About University of Tsukuba Linux User Group
About University of Tsukuba Linux User GroupAbout University of Tsukuba Linux User Group
About University of Tsukuba Linux User Group
Hirotaka Kawata
 

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) つらくないですか? 明日からはこんな感じにテスト書いてみたくないですか? @Test fun 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 とは? ● Kotlin native なテストライブラリ・フレームワーク ○ ScalaTest の影響を強く受けている ○ Kotlin Multiplatform 対応 ● 複数の機能 (後述) が独立、必要な機能だけを導入できる ○ JUnit の代わりになるテストフレームワーク全体も提供 ○ 必ずしも Kotest の Spec を使う必要はない ● Kotlin と親和性の高い強力なアサーション ○ テストメソッドが拡張関数で提供される ○ Kotest DSL による記法も可能 ● Coroutines や非同期なコードのテストのための機能も充実
  • 6. Kotest vs JUnit 5 ● 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. 明日から使える Kotest Kotest の機能は大きく分けて3つ ● Test Framework ○ JUnit のような、Kotlin native なテストフレームワーク全体 ● Assertions Library ○ hamcrest や AssertJ の代わりになる Kotlin native なアサーション ● Property Testing ○ プロパティベーステストのための仕組み 全部を使わなくてもいいんです 特に、Assertions Library は明日からでも使えます!
  • 10. Kotest の Assertions Library should___() というメソッドが基本 複数の条件を一度に指定も可能 result.shouldBe(expected) // 拡張関数 // or result shouldBe expected // Kotest DSL str.shouldContain("Kotlin") .shouldHaveMinLength(6) .shouldHaveMaxLength(10) さよなら assertEquals() Kotlin という文字列を含んだ、 6文字以上10文字以下の文字列
  • 11. とりあえず、IDE で .should してみよう めちゃくちゃ楽
  • 12. Kotest - Assertions Library の導入 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 - Inspectors Inspectors を使うと 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 elements passed but expected 1 The 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 start with "Kotlin" (diverged at index 0) [2] Message(type=TEXT, text=Kotest) => "Kotest" should start with "Kotlin" (diverged at index 3) forOne - Collection の中に ひとつだけマッチする 場合のみ成功
  • 18. 非同期なテストの例 例えばこんな例、どうやってテストしますか? // 非同期処理: 送信や処理に時間がかかる val messageId = 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-time jsonResponse.shouldEqualJson("""{ "a": true }""") response.shouldHaveHeader(“Server”, “Ktor”) instant.shouldBeBefore(Clock.System.now())
  • 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) 値を生成する Generator checkAll( 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() ハリーポッターの登場人物
  • 30. Coroutines in JUnit 5 ● `runTest` 使ってますか? ○ え、`runBlocking` 使ってるんですか? ○ 使い分けが重要: runTest は delay を無視してくれたり ● なんか面倒ですよね? @Test suspend fun testA() { ... } // Error @Test fun testA() = runTest { ... } // OK
  • 32. Kotest の Test Framework ● 以下の例は StringSpec ○ 他にもいくつかのスタイル (Spec) が用意されている ○ JUnit 風味の Spec もある (AnnotationSpec) ● テキストでテスト名を書くので、テスト結果が分かりやすい! class UserServiceTest : StringSpec({ “User can’t send text message to blocked user” { user.sendMessage(...) // coroutines ... } }) テストメソッドは 標準で suspend fun StringSpec: テスト名を文字列で管理
  • 33. テストケース、構造化してますか? class UserServiceTest { @Nested class UserServiceMessageTest { @Test suspend fun sendMessageTest() { .. } } @Nested class UserServiceFriendTest { @Test suspend 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 の Test Framework ● 柔軟なテストケースの生成 ○ テストの名前の自由度 ○ 構造化したテストケース ○ 動的なテストケースの生成 ○ テストケースごとの詳細な設定 (例: timeout, enabledIf) ● Coroutines 対応 ○ test method が標準で suspend fun ● 並列テストの設定 ○ Coroutines で実行するため JUnit より柔軟 & 高速に ● など、様々なメリット...
  • 38. その他 - Test Framework ● Mocking ○ mockk や mockito-kotlin を使ってください ○ ただし、標準では Spec 全体が Singleton のため注意 ■ mock のリセットが必要 ■ afterTest { clearMocks(repository) } ● @SpringBootTest - io.kotest.extensions:kotest-extensions-spring ○ Bean の constructor injection ができる (さよなら lateinit var) @SpringBootTest class 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 らしく ○ 謎のメソッド名のテストケースを、わかりやすく