property-based-testing について
固定例だけでなく、生成した入力で性質や不変条件を検証する開発者向けテストリファレンスです。TypeScript/JavaScript の fast-check と Python の Hypothesis を扱い、失敗例の縮小やステートフルテストも紹介します。
主な機能
- 01テスト入力の自動生成
- 02失敗ケースの縮小
- 03fast-check と Hypothesis の例
- 04性質・不変条件のテストパターン
- 05Hypothesis によるステートフルテスト
活用シーン
- 01エンコードとデコードの往復を検証
- 02データ変換の境界ケースを発見
- 03生成データで API 契約を検証
- 04不変条件に照らして操作列をテスト
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ laurigates/ claude-plugins --skill property-based-testingインストール後、Claude Code で/property-based-testingを入力して読み込まれたか確認します。
同梱ファイルREFERENCE.md
詳細
プロパティベーステスト
特定の例をテストするのではなく、コードの性質を検証するテストケースを自動生成する、プロパティベーステストの専門知識です。
このスキルを使う場面
| このスキルを使う場面... | 代わりに別のスキルを使う場面... |
|---|---|
| 数学的性質(可換性、結合性)をテストする | 特定の例に基づくユニットテストを書く |
| エンコード/デコードの往復をテストする | テストランナーの設定を行う |
| エッジケースを自動的に見つける | E2E のブラウザーテストを行う |
| データ変換と不変条件を検証する | テストの品質や問題点を分析する |
| 生成データで API コントラクトをテストする | ミューテーションテストを実行する |
主な専門分野
プロパティベーステストの概念
- 従来のテスト:特定の例をテストします
- プロパティベーステスト:すべての入力に対して成り立つべき性質をテストします
- ジェネレーター:多様なテスト入力を自動的に作成します
- シュリンキング:失敗ケースを最も単純な例に縮小します
- カバレッジ:人間が見落とす可能性のあるエッジケースを探索します
プロパティベーステストを使う場面
- 数学演算(可換性、結合性)
- エンコーダー/デコーダー(往復の性質)
- パーサーとシリアライザー
- データ変換
- API コントラクト
- 不変条件と制約
TypeScript/JavaScript(fast-check)
インストール
# Using Bun
bun add -d fast-check
# Using npm
npm install -D fast-check
基本的な例
import { test } from 'vitest'
import * as fc from 'fast-check'
// Property-based test
test('reverse twice returns original - property based', () => {
fc.assert(
fc.property(
fc.array(fc.integer()), // Generate random arrays of integers
(arr) => {
expect(reverse(reverse(arr))).toEqual(arr)
}
)
)
})
// fast-check automatically generates 100s of test cases!
主なジェネレーター(クイックリファレンス)
| ジェネレーター | 説明 |
|---|---|
fc.integer() | 任意の整数(最小値/最大値を指定可能) |
fc.nat() | 自然数(>= 0) |
fc.float() / fc.double() | 浮動小数点数 |
fc.string() | 任意の文字列(長さを指定可能) |
fc.emailAddress() | メールアドレス形式の文字列 |
fc.array(arb) | 任意の型の配列 |
fc.record({...}) | 型付きフィールドを持つオブジェクト |
fc.boolean() | ブール値 |
fc.constantFrom(...) | 選択肢から選ぶ |
fc.tuple(...) | 固定サイズのタプル |
fc.oneof(...) | ユニオン型 |
fc.option(arb) | 値または null |
fc.date() | Date オブジェクト |
よくテストするプロパティ
| プロパティ | パターン | 例 |
|---|---|---|
| 往復性 | f(g(x)) = x | エンコード/デコード、シリアライズ/解析 |
| 冪等性 | f(f(x)) = f(x) | ソート、正規化、フォーマット |
| 可換性 | f(a,b) = f(b,a) | 加算、マージ、和集合 |
| 結合性 | f(f(a,b),c) = f(a,f(b,c)) | 加算、連結 |
| 単位元 | f(x, id) = x | 1 を掛ける、0 を足す |
| 逆演算 | f(g(x)) = x | 暗号化/復号 |
設定
fc.assert(property, {
numRuns: 1000, // Run 1000 tests (default: 100)
seed: 42, // Reproducible tests
endOnFailure: true, // Stop after first failure
})
事前条件
fc.pre(b !== 0) // Skip cases where b is 0
Python(Hypothesis)
インストール
# Using uv
uv add --dev hypothesis pytest
# Optional extensions
uv add --dev hypothesis[numpy] # NumPy strategies
uv add --dev hypothesis[django] # Django model strategies
設定
# pyproject.toml
[tool.hypothesis]
max_examples = 200
deadline = 1000
[tool.hypothesis.profiles.dev]
max_examples = 50
deadline = 1000
[tool.hypothesis.profiles.ci]
max_examples = 500
deadline = 5000
verbosity = "verbose"
# Activate profile in conftest.py
from hypothesis import settings
settings.load_profile("ci")
基本的な例
from hypothesis import given, example, assume
import hypothesis.strategies as st
# Test a property
@given(st.integers(), st.integers())
def test_addition_commutative(a, b):
assert a + b == b + a
# Add explicit edge cases with @example
@given(st.integers())
@example(0)
@example(-1)
@example(2**31 - 1)
def test_with_explicit_examples(x):
assert process(x) is not None
主なストラテジー(クイックリファレンス)
| ストラテジー | 説明 |
|---|---|
st.integers() | 任意の整数(範囲を指定可能) |
st.floats() | 浮動小数点数 |
st.text() | 任意の文字列(サイズを指定可能) |
st.binary() | バイト文字列 |
st.lists(strat) | 指定したストラテジーのリスト |
st.sets(strat) | 一意な値の集合 |
st.dictionaries(k, v) | 辞書 |
st.booleans() | ブール値 |
st.sampled_from(...) | 選択肢から選ぶ |
st.tuples(...) | 固定サイズのタプル |
st.one_of(...) | ユニオン型 |
st.emails() | 有効なメールアドレス |
st.uuids() | UUID オブジェクト |
st.dates() / st.datetimes() | 日付/時刻の値 |
st.builds(Class, ...) | ストラテジーからオブジェクトを構築する |
設定(Settings)
from hypothesis import given, settings, strategies as st
@settings(max_examples=1000, deadline=None)
@given(st.lists(st.integers()))
def test_with_custom_settings(arr):
assert sort(arr) == sorted(arr)
前提条件
from hypothesis import assume
assume(b != 0) # Skip cases where b is 0
ステートフルテスト
Hypothesis は、RuleBasedStateMachine を使ったステートフルテストに対応しており、不変条件に対して一連の操作をテストできます。
CI との統合
# .github/workflows/test.yml
- name: Run hypothesis tests
run: |
uv run pytest \
--hypothesis-show-statistics \
--hypothesis-profile=ci \
--hypothesis-seed=${{ github.run_number }}
- name: Upload hypothesis database
uses: actions/upload-artifact@v4
if: failure()
with:
name: hypothesis-examples
path: .hypothesis/
クイックリファレンス
# Core decorators
@given(strategy) # Generate test inputs
@example(value) # Add explicit test case
@settings(max_examples=500) # Configure behavior
# Key helpers
assume(condition) # Skip invalid inputs
note(message) # Add debug info to failure output
target(value) # Guide generation toward value
エージェント向けの最適化
| コンテキスト | コマンド |
|---|---|
| TS テストを素早く実行 | bunx vitest --dots --bail=1 --grep 'property' |
| Python テストを素早く実行 | uv run pytest -x -q --tb=short -k 'property' |
| CI TS テスト | bunx vitest run --reporter=junit --grep 'property' |
| CI Python テスト | uv run pytest --hypothesis-profile=ci --hypothesis-show-statistics -q |
| 再現可能なテスト | fc.assert(prop, { seed: 42 }) または pytest --hypothesis-seed=42 |
| 高速な反復 | fc.assert(prop, { numRuns: 50 }) または pytest --hypothesis-profile=dev -x |
| 失敗のデバッグ | pytest -x -s --hypothesis-verbosity=debug |
| 縮小を無効化 | @settings に phases=[Phase.generate] を追加 |
詳しい例、高度なパターン、ベストプラクティスについては、REFERENCE.md を参照してください。
関連項目
vitest-testing- 単体テスト用フレームワークpython-testing- Python の pytest テストtest-quality-analysis- テストの不適切なパターンを検出mutation-testing- テストの有効性を検証
参考資料
- fast-check: https://fast-check.dev/
- Hypothesis: https://hypothesis.readthedocs.io/
- プロパティベーステスト: https://fsharpforfunandprofit.com/posts/property-based-testing/
コメント
Markdown 対応