| name | ai-nondeterministic |
| description | LLM/生成モデルが関わるテストを2局面(テストを書く道具としてのAI活用、製品に組み込まれた非決定的出力そのものの検証)で扱う。 test-catalog の手法カタログの一部。AI/LLM 支援テスト生成(観点出し、偽陽性/偽陰性対策)、 非決定的出力システムのテスト(階層化品質設計、入口/中央/出口層、スキーマ強制、メタモルフィック、複数サンプリング合意) を検証したい、または割り当てたいときに使う。通常は test-catalog スキルの索引経由で 手法が選定された後にこのスキルを直接参照する。
|
| disable-model-invocation | true |
AI と非決定的出力のテスト
LLM や生成モデルが関わるテストを2つの局面で扱う。
ひとつはテストを書く道具として LLM を使う局面、もうひとつは製品に組み込まれた非決定的出力そのものを検証する局面である。
性質ベースの自動生成テスト(PBT、差分、メタモルフィック等)は generative-property.md を参照する。
目次
AI/LLM 支援テスト生成(信頼は限定的)
概要
LLM にコードを渡してテストケースやアサーションを生成させる。
ケースの叩き台や見落とした観点の洗い出しには速いが、生成物の正しさは保証されない。
目的/いつ使う
テストの初稿、命名、定型的な it.each の量産、観点のブレインストーミングに使う。
但し書き(必須): LLM が生成したアサーションは、実装の現在の挙動をそのまま「正解」と思い込んで固定しがちで、バグごと緑にする。生成テストは必ず人がレビューし、期待値の根拠を仕様に照らして確認する。 oracle を LLM に委ねてはならない。
正しさを数学的かつ網羅的に保証したい critical な箇所には使わず、PBT、差分、形式検証連携を使う。
手順(コード化しにくいので手順で)
- 対象関数のシグネチャと仕様(あれば)を渡し、観点(正常、境界、異常)ごとにケース候補を出させる。
- 生成された期待値を実装ではなく仕様と突き合わせて検証する。実装と一致するだけのアサーションは捨てる。
- 性質として一般化できるものは PBT へ、相互作用はコンビナトリアルへ昇格させ、具体例だけを手書きテストに残す。
- レビューを通ったものだけコミットする。生成のまま無検証で取り込まない。
遂行手順(着手→完了)
この作業の本体は「LLM に観点を出させ、人が偽陽性/偽陰性を除き、残った観点を台帳の T-ID に落としてテスト化する」ことだ。LLM は観点の母集団を出す係で、採否の oracle ではない。次の順で進める。
- 観点リストを LLM に出させる:対象関数のシグネチャと仕様(あれば)を渡し、正常・境界・異常の各区分でケース候補を列挙させる。各候補に「なぜそれが要るか」の根拠を一緒に出させる(根拠は採否の判断材料であって、それ自体は証跡ではない)。
- 人がレビューして偽陽性/偽陰性を除く:出た候補を1件ずつ、根拠を仕様に照らして取捨する。仕様に裏付けのないもの(偽陽性)を落とし、仕様にあるのに候補から漏れた観点(偽陰性)を人が足す。LLM が「これで網羅」と言っても証跡にしない——確率的生成に網羅保証はない。
- 残った観点を台帳へ登録する:採用した観点を1件ずつ台帳(
../test-extract/assets/test-extract-template.md)の行にし、各々へ T-ID を採番する。観点と T-ID は1対1にする(1観点に複数 T-ID が要るなら観点が粗い、逆なら重複)。
- T-ID をテスト化する:各 T-ID に対応するテストを書く。期待値は実装でなく仕様と突き合わせ、実装の現挙動を写しただけのアサーションは捨てる。性質化できるものは PBT、相互作用はコンビナトリアルへ昇格させ(
generative-property.md)、具体例だけ手書きに残す。
- レビューを通ったものだけコミットする:生成のまま無検証で取り込まない。
完了チェック(もれ確認)
- 各候補の根拠を確認したか:LLM が出した候補のうちコミットに残ったものすべてに、人が仕様で裏付けを取った形跡があるか。根拠未確認のまま採ったものは偽陽性の温床。
- 観点リストの全項目が T-ID に落ちたか:手順2で採用した観点の数と、台帳の T-ID 数が一致するか(観点 → T-ID の正引き)。差があれば観点の取りこぼし。
- 全 T-ID が観点へ逆引きできるか:台帳の各 T-ID から、それが埋める観点が一意に辿れるか(T-ID → 観点の逆引き)。観点に紐づかない T-ID は、根拠不明のまま増えたテスト。
- 期待値が仕様由来か:各テストのアサーションが仕様に照らした値で、実装の現挙動の写しでないか。写しは回帰検出はできてもバグ検出ができない(落とし穴の1点目)。
- LLM の自信を証跡にしていないか:「LLM が網羅と判断した」を停止根拠にしていないか。停止は人が観点リストを埋めたと確認したときだけ。網羅の保証は昇格先の体系的技法が担い、この工程では主張しない。
落とし穴
- 生成アサーションが現状の実装を追認するだけになり、回帰検出はできてもバグ検出ができない。
- もっともらしいが微妙に誤った期待値を量産する。レビューのコストを織り込まないと「テストはあるのに守られていない」状態になる。
AI に指摘とレビューをさせるときの偽陽性対策
テストの抜けレビューや、仕様とテストケースの整合チェックを AI に「指摘させる」と、見落とし(偽陰性)は少ない代わりに偽陽性が多い。
偽陽性を放置すると指摘が信用されず、レビュー自体が無視される(狼少年化)。
偽陰性を保ったまま偽陽性を絞る3手を、強い順に効かせる。
- 欠陥でないものを明示して定義を狭める(ネガティブプロンプト):「XX は欠陥ではない」を例とともにプロンプトへ足す。初期は少数サンプルで指摘を回し、出た偽陽性をそのまま「欠陥でない」例に積んでいくと速く収束する。最も手軽で効く。
- 深刻度を出させて閾値で切る:各指摘に深刻度(低/中/高)を出力させ(JSON で
severity を強制)、閾値以上だけ採る。低深刻度に偏る偽陽性をまとめて落とせる。
- 形式モデルへ翻訳させてから指摘させる(形式化 thinking):自然言語のまま是非を問うと曖昧な判断で誤検知が増える。先に対象を状態機械、遷移表、事前条件と事後条件、不変条件へ翻訳させ、その形式モデル上の違反として指摘させる。論理的整合性が要る仕様とテストケースのレビューで効く。翻訳先のモデルを本気で検査したいなら AI に留めず
loop-engineering(TLA+)や formal-verification(Lean)へ渡す。
いずれも最終判定は人または決定論的検証器に置く。AI は指摘の母集団を出す係で、採否の oracle にはしない(上の但し書きと同じ原則)。
「網羅」の扱い(網羅は主張しない)
- なぜ網羅を定義できないか:生成は確率的で観点の全数を保証できず、合否(oracle)を LLM に委ねるとバグごと緑化するため。
- 停止規範:観点リスト(正常、境界、異常)を人が定義し、それを LLM 出力で埋めたかを人がチェックしたら打ち切る。各生成アサーションは実装ではなく仕様と突き合わせる。
- 体系的技法への昇格:性質化できるものは PBT、相互作用はコンビナトリアル、具体例だけ手書きへ移し、レビューを通ったものだけコミットする。網羅の保証はあくまで昇格先の A型技法が担う。
非決定的出力システムのテスト(階層化品質設計)
概要
LLM や生成モデルなど、同じ入力でも出力が毎回揺れる(非決定的な)部品を組み込んだシステムをテストする設計。
前項「AI/LLM 支援テスト生成」がテストを書く道具としての LLM の話なのに対し、こちらは製品に組み込まれた LLM 出力そのものを検証する。
揺らぎは AI の価値(探索や多様性)なので消さない。
代わりに、揺らぎを許す層と保証を固定する層を分け、層ごとに何を保証するか(保証の所在)を明示する。
これが階層化品質設計で、揺らぎを一つの層に閉じ込め、その外側を決定論的に固める。
目的/いつ使う
出力が確率的で、toBe の固定値アサートが原理的に書けない部品をプロダクトに載せるときに使う。
チャット応答、要約、分類、コード生成、検索ランキングなど。
出力が決定論的なら本項は過剰で、通常の出力ベーステストで足りる(YAGNI)。
手順(層に分けて保証を割り当てる)
揺らぎを中央の層に閉じ込め、その入口と出口を決定論的に固める三層で考える。
- 入口層(決定論。厳密に固める):モデルへ渡す前の入力検証、正規化、プロンプト構築を純関数に切り出し、出力ベースで網羅テストする。揺らぎはここに無い。同値分割と境界値がそのまま効く。
- 中央層(揺らぎを許すが、性質で縛る):モデル出力そのものは固定値で検証せず、破れてはならない性質で締める。
- 構造/スキーマ強制:出力を必ずスキーマ(JSON Schema, zod 等)に通し、形が満たされることを検証する。形が崩れたら失敗。これが最初の関門。
- 不変条件(メタモルフィック):「入力を言い換えても分類は不変」「要約は原文より短い」など、揺らいでも成り立つ関係を property 化する(メタモルフィックや PBT は
generative-property.md を流用)。
- 複数サンプリングの合意:同入力を N 回引き、多数決や一致率で安定性を測る。閾値割れを失敗とする。
- 判定の外部化:合否を別の決定論的検証器(ルール、正規表現、参照実装)で下す。合否を被検査モデル自身に委ねない。
- 出口層(決定論。運用で受ける):中央層をすり抜けた異常を本番で受け止める。出力のガードレール(後段バリデーション)、可観測性(入出力ログ)、フィードバックでのリグレッション資産化、最終的な人間の判断点を置く。
遂行手順(着手→完了)
三層は「揺らぎをどこに閉じ込め、どこを固定するか」の線引きそのものだ。層ごとに何ケース作れば完了かを決めながら進める。
- 層の境界を引く:被検査部品の入出力を辿り、純関数で書ける前後処理(入口=入力検証・正規化・プロンプト構築、出口=後段バリデーション・ガードレール)と、モデル呼び出しそのもの(中央)を物理的に分離する。分離できないと固定層に揺らぎが漏れる。まずコードを切り出してから先へ進む。
- 入口層を決定論で網羅する:切り出した入力検証・正規化・プロンプト構築に、同値分割と境界値で出力ベーステストを書く。何ケースで完了か=同値クラスの代表 × 各境界の内外。プロンプト構築なら「必須項目欠落」「最大長」「特殊文字」など分岐の各枝を1ケースずつ踏む。ここは
toBe の固定値で締める。
- 中央層を性質で締める(固定値は使わない):破れてはならない性質を列挙し、各々を property 化する。最低限「構造/スキーマ強制」「不変条件(メタモルフィック)」「複数サンプリングの合意」の3系統を1本以上ずつ。何を固定し何を許容差で見るかをここで決める——スキーマ適合は真偽で固定、意味的な近さは類似度の許容差(後述の閾値)で判定する。判定は被検査モデルでなく決定論的検証器に置く。
- 許容差と閾値を数値で確定する:許容差判定(意味的類似度・構造一致・多数決の一致率)それぞれに、合格ラインの数値を1つ決めて定数化する。閾値は当て推量でなく、既知の良サンプル群と既知の悪サンプル群を流して、両者を分離できる値に校正する(物理の校正と同じく、根拠ある一点を残す)。閾値割れを失敗とする。
- 出口層を決定論で網羅する:後段バリデーション・ガードレールに、入口層と同じく同値分割・境界値でテストを書く。中央層をすり抜ける既知の異常(スキーマは通るが値域外、禁止語、空応答)を各1ケース踏む。
- 評価データセットに全観点を載せる:中央層の性質検査は固定の評価データセット(入力と、各層で見る観点のラベル)に対して回す。手順2〜5で挙げた観点が1つ残らずデータセットの行として存在するかを突き合わせ、欠けを足してから打ち切る。
完了チェック(もれ確認)
- 層が物理分離されているか:入口・出口の前後処理がモデル呼び出しと別関数に切れていて、固定層のテストがモデルを一切呼ばずに緑になるか。モデルを呼ばないと再現しないなら境界が漏れている(手順1の戻り)。
- 決定層が再現するか:temperature と seed を固定した入口・出口層のテストを2回連続で流して、同一結果で緑か。揺らげば固定すべき層に非決定が紛れている。
- 許容差の閾値が決まっているか:中央層の各許容差判定(類似度・構造一致・一致率)に数値の合格ラインが定数として存在し、良サンプル/悪サンプルで校正した根拠があるか。マジックナンバーが裸で埋まっていれば未確定。
- 中央層が3系統を満たすか:構造/スキーマ・不変条件・複数サンプリング合意のそれぞれに性質が1本以上あるか。スキーマだけなら「形は正しいが内容が誤った出力」を通す(落とし穴の3点目)。
- 判定核が決定論側にあるか:中央層の合否を下す検証器が、被検査モデルでなくルール/正規表現/参照実装か。
grep で LLM-as-judge の呼び出しが最終判定に使われていないか逆引きする。
- 評価データセットに全観点が乗ったか:手順2〜5で挙げた観点(入口の同値クラス・境界、中央の各性質、出口の既知異常)の数と、評価データセットの観点ラベルの数が一致するか。差があれば観点漏れ=その揺らぎは検査されていない。
落とし穴
- 揺らぎを消そうとして temperature=0 等で固定し、スナップショットで縛る。決定論化は再現性を上げるが、本番の揺らぎを検査しないので保証にならない。固定は回帰検出用、性質検査は別に要る。
- 合否をモデル自身(LLM-as-judge)だけに委ね、判定核まで揺らがせる。保証の所在が揺らいだら全体が崩れる。最終判定は決定論的検証器に置く。
- 中央層の性質が弱い(スキーマが通るだけ)と、形は正しいが内容が誤った出力を通す。構造、不変条件、合意の複数で締める。
「網羅」の扱い(揺らぎ層は網羅できない、外側を網羅する)
- なぜ網羅を定義できないか:中央層の出力空間は確率的で開いており、全数を踏めない。揺らぎ層に網羅率は定義できない。
- 網羅の付け替え:網羅は**入口層と出口層(決定論)**で取る。入力検証、プロンプト構築、後段バリデーションは A型技法(同値分割、境界値、C1)で網羅し、中央層は性質の本数と、複数サンプリングの試行や閾値を停止規範にする。
- 保証の所在を表に書く:どの層が何を保証するかを台帳(
../test-extract/assets/test-extract-template.md の用語定義表の隣)に明示し、「揺らぎを許す層」と「固定する層」の境界を曖昧にしない。境界が曖昧な設計は、揺らぎが保証側へ漏れる。