プロパティベーステスト
ランダムな入力を大量に生成し、プログラムの不変条件が常に成り立つことを検証するテスト手法
テスト品質
プロパティベーステストとは
プロパティベーステスト (PBT) は、ランダムな入力を大量に生成し、プログラムの不変条件 (プロパティ) が常に成り立つことを検証するテスト手法である。Haskell の QuickCheck が起源。fast-check が TypeScript の代表的なライブラリ。
例ベーステスト vs プロパティベーステスト
| 観点 | 例ベース | プロパティベース |
|---|---|---|
| 入力 | 開発者が選んだ具体値 | ランダムに自動生成 |
| テスト数 | 数個〜数十個 | 数百〜数千 |
| エッジケース | 開発者が思いつく範囲 | 自動的に発見 |
| 検証 | 具体的な期待値 | 不変条件 (プロパティ) |
fast-check での例
import fc from 'fast-check';
// プロパティ: ソートした配列は元の配列と同じ要素を持つ
test('sort preserves elements', () => {
fc.assert(
fc.property(fc.array(fc.integer()), (arr) => {
const sorted = [...arr].sort((a, b) => a - b);
expect(sorted.length).toBe(arr.length);
expect(sorted.every(x => arr.includes(x))).toBe(true);
})
);
});
// プロパティ: JSON.parse(JSON.stringify(x)) === x (ラウンドトリップ)
test('JSON roundtrip', () => {
fc.assert(
fc.property(fc.jsonValue(), (value) => {
expect(JSON.parse(JSON.stringify(value))).toEqual(value);
})
);
});
代表的なプロパティ
| プロパティ | 例 |
|---|---|
| ラウンドトリップ | encode → decode で元に戻る |
| 冪等性 | f(f(x)) === f(x) |
| 不変条件 | ソート後は昇順 |
| 可換性 | a + b === b + a |
| モデルベース | 実装とリファレンスが同じ結果 |
シュリンキング
テスト失敗: [42, -7, 0, 100, -3] で失敗
↓ シュリンキング (最小の反例を探索)
最小反例: [-1] で失敗
→ 負の数の処理にバグがある
fast-check は失敗した入力を自動的に縮小し、最小の反例を報告する。
DynamoDB のバリデーションテスト
test('全ての有効な注文がバリデーションを通過', () => {
fc.assert(
fc.property(
fc.record({
productId: fc.uuid(),
quantity: fc.integer({ min: 1, max: 100 }),
email: fc.emailAddress(),
}),
(order) => {
expect(validateOrder(order).success).toBe(true);
}
)
);
});
いつ使うか
| ケース | 推奨 |
|---|---|
| パーサー、シリアライザー | ✅ ラウンドトリップ |
| バリデーション | ✅ 境界値の自動発見 |
| アルゴリズム | ✅ 不変条件の検証 |
| UI テスト | ❌ 例ベースの方が適切 |
さらに掘り下げるなら関連書籍が参考になる。