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
59
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
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
21
1.2k
Measuring & Analyzing Core Web Vitals
bluesmoon
9
660
Building an army of robots
kneath
306
46k
Art, The Web, and Tiny UX
lynnandtonic
303
21k
Optimizing for Happiness
mojombo
379
70k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
37
2.6k
Context Engineering - Making Every Token Count
addyosmani
9
380
Building Adaptive Systems
keathley
44
2.8k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
31
2.7k
GraphQLの誤解/rethinking-graphql
sonatard
73
11k
Making Projects Easy
brettharned
120
6.4k
Stop Working from a Prison Cell
hatefulcrawdad
272
21k
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をもとに対象のデータを作る