Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Getting_Started_with_Property_Based_Testing.pdf
Search
jsoizo
November 14, 2025
0
60
Getting_Started_with_Property_Based_Testing.pdf
jsoizo
November 14, 2025
Tweet
Share
More Decks by jsoizo
See All by jsoizo
kotlinxライブラリの歩き方 〜 Kotlin公式エコシステムを使いこなす〜
jsoizo
1
130
はじめて関数型言語の機能に触れるエンジニア向けの学び方/教え方 / how-to-learn-or-teach-for-fp-beginner
jsoizo
5
1.3k
よくつかっているIterableの自作extensionを紹介します
jsoizo
0
90
HARD THINGS in Ad-Tech Engineering
jsoizo
1
990
“エンジニア35才定年説に挑戦する” 開発チームのマネジメント
jsoizo
41
16k
drone.ioを使って docker build & push自動化
jsoizo
0
2.5k
Featured
See All Featured
Navigating Team Friction
lara
190
15k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
127
54k
What’s in a name? Adding method to the madness
productmarketing
PRO
24
3.8k
Scaling GitHub
holman
463
140k
Dealing with People You Can't Stand - Big Design 2015
cassininazir
367
27k
Mobile First: as difficult as doing things right
swwweet
225
10k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
31
2.7k
Building Flexible Design Systems
yeseniaperezcruz
329
39k
How STYLIGHT went responsive
nonsquared
100
5.9k
A better future with KSS
kneath
239
18k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
285
14k
Music & Morning Musume
bryan
46
6.9k
Transcript
小さくはじめる Property Based Testing 2025年11月15日 JJUG CCC 2025 Fall 関根
純 @jsoizo
2 経歴 インターネット業界でソフトウェアエンジニアとして働いています。 スケーラブルなシステムを作ること、Kotlinプログラミングが好き。 kotlin-csvというPure Kotlinなcsvパーサをメンテしています。 自己紹介 関根 純 せきね じゅん
2023.01 コドモンに開発エンジニアとして入社 2024.01 育児休業(6ヶ月) 様々な副作用と向き合う 2025.01 新規プロダクトや基盤開発に従事
3 今日話すこと 探索的テストの必要性 Property Based Testing(PBT)の解説とサンプル Propertyの見つけ方 1 2 3
4 CONFIDENTIAL - © 2022 CoDMON Inc. 4 テストを”増やす”ことが正解とも限らない このような障害報告を書いた経験がありませんか?
✋ • 原因: 数値の計算ロジックに想定外のバグが混入 • 再発防止策:テストを更にたくさん作る → 正しいのだが、”むやみに” テストを増やすことは プログラムの 修正コストを 上げてしまうことも ある。 (実装の詳細に踏み込みすぎてしまうなど)
5 CONFIDENTIAL - © 2022 CoDMON Inc. 5 テストを”増やす”ことが正解とも限らない このような障害報告を書いた経験がありませんか?
✋ • 原因: 数値の計算ロジックに想定外のバグが混入 • 再発防止策:テストを更にたくさん作る → 正しいのだが、”むやみに” テストを増やすことは プログラムの 修正コストを 上げてしまうことも ある。 (実装の詳細に踏み込みすぎてしまうなど) スマートな方法は ないかな???
6 CONFIDENTIAL - © 2022 CoDMON Inc. 6 知らないことに目を向ける Unknown
Knowns = 知っていることを しらない (あたりまえ) Unknown Unknowns = 知らないことを しらない (無知) Known Unknowns = 知らないことを しっている (調査すべきこと) Known Knowns = 知っていることを しっている (定着した知識) 知っている(知識がある, 想像できる) 知らない(知識がない, 想像できない) 知っている (意識してる) 知らない (意識してない)
7 CONFIDENTIAL - © 2022 CoDMON Inc. 7 Unknown Knowns
= 知っていることを しらない (あたりまえ) Unknown Unknowns = 知らないことを しらない (無知) Known Unknowns = 知らないことを しっている (調査すべきこと) Known Knowns = 知っていることを しっている (定着した知識) 知っている(知識がある, 想像できる) 知らない(知識がない, 想像できない) 知っている (意識してる) 知らない (意識してない) いわゆる入力例を考えてテストするのはここ 知らないことに目を向ける
8 CONFIDENTIAL - © 2022 CoDMON Inc. 8 Unknown Knowns
= 知っていることを しらない (あたりまえ) Unknown Unknowns = 知らないことを しらない (無知) Known Unknowns = 知らないことを しっている (調査すべきこと) Known Knowns = 知っていることを しっている (定着した知識) 知っている(知識がある, 想像できる) 知らない(知識がない, 想像できない) しっている (意識してる) しらない (意識してない) 知らないことに目を向ける 理解してないこと=リスクに対して 探索的に向き合う必要がある
9 CONFIDENTIAL - © 2022 CoDMON Inc. 9 探索し、知っていることの幅を広げる Unknown
Knowns = 知っていることを しらない (あたりまえ) Unknown Unknowns = 知らないことを しらない (無知) Known Unknowns 知っている(知識がある, 想像できる) しっている (意識してる) しらない (意識してない) Known Knowns = 知っていることを しっている (定着した知識) 知らない(知識がない, 想像できない) 知っていることを 増やすことで リスクを減らせる そのための Property Based Testing
10 CONFIDENTIAL - © 2022 CoDMON Inc. 10 Property Based
Testing(PBT)とは?? このような特徴をもっているテスト手法 1. 任意の処理に対しランダムな入力(Arbitrary)を与える 2. 処理の結果が満たす共通の性質(Property)を検証 3. テストが失敗したらよりシンプルな失敗パターンを出す → これにより、探索的にテストができる
11 CONFIDENTIAL - © 2022 CoDMON Inc. 11 Property Based
Testing(PBT)とは?? • • • • • • • 入力の集まり • • • • • • • 出力の集まり 処理 性質(Property) すべての出力が 満たすべき条件 • • 任意の入力値を生成(Arbitrary)し、 その値をもって処理を実行した 出力に対する性質(Property)を検証する
12 CONFIDENTIAL - © 2022 CoDMON Inc. 12 具体例:reverse(list: List<T>)
• • • • • 入力の集まり • • • • • • 出力の集まり 処理 reverse 性質(Property) ☑ 要素数が入力が同じ ☑ 二回で入力にもどる ☑ 入力同じ要素を持つ • [1,2,3] [100] [7,8,7] [4,6,3] [] [3,2,1] [100] [7,8,7] [3,6,4] [0] [9,99] [99, 9] • []
13 CONFIDENTIAL - © 2022 CoDMON Inc. 13 テストが失敗したとき 入力(X0~Xn)を徐々に小さくしていく(Shrinking)
小さく=リストの要素数、Intの値、文字数など 入力の集まり 失敗する入力 • X0 • X0 • X1 • X2 • Xn … 失敗する値の中の最小値 = バグが起きる境界 縮小化 Shrinking • Xn+1 • X1
14 CONFIDENTIAL - © 2022 CoDMON Inc. 14 PBTを書く方法 •
JUnit Platformで動作するPBTをサポートしたテストFW ◦ [Java] jqwik ( https://jqwik.net/ ) ◦ [Kotlin] Kotest ( https://kotest.io/ ) • Kotestに限ると ◦ PBTの機能を単体で簡単に利用できる ◦ = テストFWはJUnit 5だがPBT部分だけKotestでも良い ※ 次頁からKotlinによる実装サンプルを出しますが補足説明を入れてます
15 CONFIDENTIAL - © 2022 CoDMON Inc. 15 サンプル ~テスト対象の関数~
fun <T> buggyReverse(list: List<T>): List<T> { val result = MutableList<T>() // バグ: index = 0 まで行くべきところを 1 で止めてしまう for (index in list.size - 1 downTo 1) { result.add(list[i]) } return result } 配列の末尾からindex=1まで走査 → index=0の要素が飛ぶ
16 CONFIDENTIAL - © 2022 CoDMON Inc. 16 サンプル ~テストコード~
@Test fun `reverse2回で元にもどる`() = runTest { checkAll<List<Int>> { list -> val reversed = buggyReverse(list) assert(buggyReverse(reversed) == list) } }// ※ runTest{} はKotlinの非同期関数を呼び出すためのおまじないです このようにテストを書くことができる
17 CONFIDENTIAL - © 2022 CoDMON Inc. 17 @Test fun
`reverse2回で元にもどる`() = runTest { checkAll<List<Int>> { list -> val reversed = buggyReverse(list) assert(buggyReverse(reversed) == list) } } 任意の<List<Int>を生成 =Arbitrary ブロック内を繰り返す サンプル ~テストコード~
18 CONFIDENTIAL - © 2022 CoDMON Inc. 18 @Test fun
`reverse2回で元にもどる`() = runTest { checkAll<List<Int>> { list -> val reversed = buggyReverse(list) assert(buggyReverse(reversed) == list) } } 生成されたList<Int>の値で テスト対象の処理を実行し サンプル ~テストコード~
19 CONFIDENTIAL - © 2022 CoDMON Inc. 19 @Test fun
`reverse2回で元にもどる`() = runTest { checkAll<List<Int>> { list -> val reversed = buggyReverse(list) assert(buggyReverse(reversed) == list) } } 満たすべき共通の性質を検証 = Property サンプル ~テストコード~
20 CONFIDENTIAL - © 2022 CoDMON Inc. 20 失敗 →
Shrinkingのログ Property test failed for inputs 0) [922145860, 862193514, ...and 63 more Attempting to shrink arg [922145860, 862193514, ...and 63 Shrink #1: [922145860] fail Shrink #2: [] pass Shrink result (after 2 shrinks) => [922145860] 1. ランダムな配列で失敗 2. Shrinkingを開始 3. 配列の要素数=1が 最小の失敗とわかる
21 CONFIDENTIAL - © 2022 CoDMON Inc. 21 Q. ところでProperty見つけるの難しくないですか?
A. はい!むずかしいです!!! でも、見つけ方もあります
22 CONFIDENTIAL - © 2022 CoDMON Inc. 22 Propertyの見つけ方 Property(=処理結果が満たす共通の性質)を見出すパターン
1. 不変条件に着目する 2. 同じ目的の既存実装を使う 3. 関係 性から 考える 詳細はこの本の3章にあります Fred Hebert 著、山口能迪 訳 『実践プロパティベーステスト ― PropErとErlang/Elixirではじめよう』 (2023年)
23 CONFIDENTIAL - © 2022 CoDMON Inc. 23 1. 不変条件に着目する
例:消費税率の計算 1~100万の範囲内でランダム入力 fun withTax(price: Int): Int = (price * (1 + 0.10)).toInt() checkAll(Arb.int(1..1_000_000)) { price -> // 1円~100万円 val priceWithTax = withTax(price) // Property1: 税込価格は必ず税抜価格以上 assert(priceWithTax >= price) }
24 CONFIDENTIAL - © 2022 CoDMON Inc. 24 1. 不変条件に着目する
例:消費税率の計算 // Property2: 逆算すると元の価格に戻る(誤差1円以内) val reversedPrice = (priceWithTax / (1 + taxRate)).toInt() assert(abs(reversedPrice - price) <= 1) // Property3: 税額は0以上となる assert(priceWithTax − price >= 0)
25 CONFIDENTIAL - © 2022 CoDMON Inc. 25 2. 同じ目的の既存実装を使う
例:JSONエンコーダの置き換え 任意のuser値をランダム入力 checkAll(userArb()) { user -> val oldResult: String = LegacyJsonEncoder.encode(user) val newResult: String = NewJsonEncoder.encode(user) // Property: 新旧の異なるエンコーダから同じ結果が得られる oldResult.shouldEqualJson(newResult) } // ※ shouldEqualJsonはKotestのJSON matcher
26 CONFIDENTIAL - © 2022 CoDMON Inc. 26 3. 関係
性から 考える 例:在庫(warehouse)と注文(order)の関係性 checkAll(Arb.list(orderArb())) { orders -> // 注文の配列を生成 val warehouse = Warehouse(initialStock = 100) // 注文数ぶん在庫から引き当てたら orders.forEach { warehouse.ship(it) } // Property: 在庫の残数は初期在庫から注文総数を引いた数と一致 assert(warehouse.remain == 100 − orders.sumOf(it.quantity)) } // ※ sumOfは配列内の要素の特定フィールドの合計を取る
27 CONFIDENTIAL - © 2022 CoDMON Inc. 27 まとめ •
自分の知識の外側を探索的にテストしたほうがよい • Property Based Testing(PBT)はそのための良い手段 • JUnit系のFWであればKotestで簡単にPBTできる • Propertyを見つけるのは難しいが見つけ方もある Property Based Testingを試してみましょう!!
28 ご清聴ありがとうございました! 🙇 アンケートも回答おねがいします 🙇 全体アンケート セッションアンケート
29 CONFIDENTIAL - © 2022 CoDMON Inc. 29 Appendix: JUnit
x Kotestするために gradleならbuild.gradle.ktsに依存ライブラリの宣言を追加 Kotlinの非同期関数を呼び出す ためのおまじないが必要 (Coroutineスコープの作成) plugins { // Kotlinを利用するためKotlin Pluginが必要 alias("org.jetbrains.kotlin.jvm:2.2.20") } dependencies { // JUnit5のテストFW, Kotlin非同期処理, Kotest PBT testImplementation("org.junit.jupiter:junit-jupiter:5.10.0") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0") testImplementation("io.kotest:kotest-property-jvm:5.9.1") }
30 CONFIDENTIAL - © 2022 CoDMON Inc. 30 Appendix: JUnit
x Kotestするときの注意点 @Test fun `reverse2回で元にもどる`() = runTest { checkAll<List<Int>> { list -> val reversed = buggyReverse(list) assert(buggyReverse(reversed) == list) } } Kotlinの非同期関数を呼び出す ためのおまじないが必要 (Coroutineスコープの作成) JUnitからKotestのPBT機能を呼び出す際にはひと手間必要
31 CONFIDENTIAL - © 2022 CoDMON Inc. 31 Appendix: KotestのTips
(1) • デフォルトでいくつかの型用のArbitraryが用意されてる ◦ 境界値が定められており使うことが保証される ▪ Int: 0, 1, -1, Int.MAX_VALUE, Int.MIN_VALUE ▪ List<T>: 空, 1要素, 重複含む複数要素 • その他にもArbitrary用のユーティリティ多いので便利 ◦ Generators List | Kotest
32 CONFIDENTIAL - © 2022 CoDMON Inc. 32 Appendix: KotestのTips
(2) • カスタムのArbitraryを作ることも可 ◦ ドメイン型等でテストするときに有用 data class User(val id: UUID, val name: String) // ※ Javaのrecord相当 val userArb: Arb<User> = arbitrary { val id: UUID = Arb.uuid(UUIDVersion.V4).bind() val name: String = Arb.int().map { "User_$it" }.bind() User(id, name) } 各フィールドの型に沿った Arbをもとに対象のデータを作る