凍結した 176 問。golden master と呼んでいるが、期待は出力を写していない
176 frozen questions: we call it a golden master, but the expectations don't copy the output
D19-4-T38-golden-master・技術・日本語入力でプレイする仕組み
港で佐伯に「アリバイは」とだけ入力したら、アリバイの証言に着地する。
こういう入力と正解の組を 176 問、8 月 3 日に凍結して、毎周流しています。
うちはこれを golden master と呼んでいます。
ただ、名前の元の方法とは一か所、大事な所が違います。
この回の位置と、前後の回#
区切り: T34〜T49 確かめ方(16 回のうち 5 回目)
先に読むとよい回: T37(台本の中に期待を書く)
次に読む回: T39(言い換えても着地するか)
名前 — golden master testing#
- 正式名
- golden master testing
- 別名
- characterization testing(特性テスト)、approval testing、snapshot testing
- 日本語
- ゴールデンマスター、特性テスト、承認テスト
- 初出
- characterization test は Michael C. Feathers の "Working Effectively with Legacy Code"(2004〜2005 年。資料によって年が違います)で名付けられた語です。
golden master という語を最初に使った文献は、検索で確かめられませんでした。
approval testing は Llewellyn Falco の ApprovalTests というライブラリで広まった呼び名です。
golden master とは#
テストを書くには、正しい出力を知っている必要があります。
ところが、古いコードや出力の大きいコードでは、正しい出力を誰も書けないことが多い。
Feathers はここで発想を逆にしました。
「正しいかどうかは置いておく。いま実際にこう動いている、を記録する」
手順はこうです。
- いまのコードに入力を流す。
- 出た出力をそのまま保存する(これが golden master)。
- コードを変えたら、同じ入力を流し、保存した出力と一致するかを見る。
- 違えば、人が見て「意図した変更か」を決める。意図したなら保存し直す。
Feathers の定義は「コードが何をするべきかではなく、実際に何をするかを記録するテスト」です。
目的はバグを見つけることではありません。
リファクタリングの間、振る舞いが変わっていないことを確かめる安全網です。
approval testing は、この手順の 4 を道具にしたものです。
出力の差分を人に見せ、「承認」したら新しい基準になる。
画像、HTML、JSON、ログのような大きい出力でよく使われます。
フロントエンドの snapshot テストも同じ形です。
よく知られた弱点は三つです。
- 今の出力にバグがあれば、そのバグも正解として固定される。
- 差分が出たとき、人が中身を読まずに「承認」しがち。approval testing のこの弱点には Guru Checks Output という名前が付いています(この連載では T54 で書きます)。
- 出力に時刻や乱数のように毎回変わる部分があると、毎回差分が出る。
うちの golden/dev#
Game/adv/golden/dev/ch1.jsonl です。
1 行が 1 問の JSON で、# で始まる行は注記です。
冒頭の数行を写します。
golden dev — 章1(凍結版 2026-08-03。speaker=対話相手。凍結後は編集禁止=02仕様)
{"input":"事件当夜の所在を聞かせてもらえますか","state":{"where":"port","speaker":"saeki","phase":1,"timeOfDay":"day","flags":[]},"expect":"saeki.alibi_night"}
{"input":"昨夜の足取りを説明してくれ","state":{"where":"port","speaker":"saeki","phase":1,"timeOfDay":"day","flags":[]},"expect":"saeki.alibi_night#alibi_s0"}
{"input":"アリバイは","state":{"where":"port","speaker":"saeki","phase":1,"timeOfDay":"day","flags":[]},"expect":"saeki.alibi_night"}
{"input":"今日は流氷が来てるな","state":{"where":"port","speaker":"saeki","phase":1,"timeOfDay":"day","flags":[]},"expect":"none"}
{"input":"番屋を出た時刻に嘘があるだろう","state":{"where":"port","speaker":"saeki","phase":1,"timeOfDay":"day","flags":[]},"expect":"saeki.confront_alibi"}
1 問が持つのは三つです。
- input … 入力された日本語
- state … どこで(where)、誰と(speaker)、何章で(phase)、昼か夜か(timeOfDay)、どのフラグが立っているか(flags)
- expect … 着地するべき先。意図の id、段まで縛るなら「意図#段」、進んではいけないなら none
今日数えた内訳です。
176 問のうち、意図だけを期待するもの 114、段まで期待するもの 28、none が 34。
相手は佐伯 42、堂本 36、ハル 33、岬 26、源三 20、相手なし(現場を調べる入力)19。
フラグを持った状態から始まる問いが 15 あります。
全部が第一章です。
何を確かめるか#
adv-sim golden は、1 問ごとに状態を組み立てて、照合器を 1 回呼ぶだけです。
セッション(移動・手帳・聞き返しへの返事)は通しません。
main.cpp の CmdGoldenJudge から、判定の芯を写します。
kaede::adv::AdvState State;
State.where = C.Where;
State.speaker = C.Speaker;
State.phase = C.Phase;
State.timeOfDay = C.TimeOfDay;
State.flags = { C.Flags.begin(), C.Flags.end() };
const kaede::adv::MatchResult R = Matcher.query(C.Input, State, Engine.data, Th);
const bool bAccepted = (R.Kind == kaede::adv::MatchResult::EKind::Accepted);
// 「取り違え」の本体は進行が動くこと。雑談のように何も立てない応答は、
// 拾われても事件の状態を変えないので誤検出ではない(設計 08/04/05)。
const bool bAdvanced = bAccepted && R.Stage >= 0 &&
!R.IntentPtr->stages[R.Stage].sets.empty();
none の問いの扱いが要です。
if (C.Expect == "none")
{
bOk = !bAdvanced;
if (bAdvanced) { ++NegFp; }
if (bAccepted && !bAdvanced) { ++NegChat; }
...
}
none は「何にも当たるな」ではなく「事件を進めるな」です。
「今日は流氷が来てるな」が雑談の応答に拾われるのは正解に数えます(CHAT)。
進行のフラグを立てる段に当たったときだけ、誤り(NEG_FP)です。
ほかに数えているものを挙げます。
- REACH … 1 回で正解した数に、「聞き返しの相手が正解の意図だった」数を足したもの。「はい」と返せば届くからです。
- NEG_F1 … none の問いに聞き返してしまった数。誤りではないが、雑音です。
- LOCK_OK … 段 0(材料が無いうちは突きつけに進まない段)を期待する問いが、ちゃんと段 0 に止まった数。
- THIN … 1 位と 2 位の差が 0.10 未満で採用した数と、その中の誤り。
合格ラインは作品の目録(runs/suite.json)にあり、ツールの中にはありません。
"dev": {
"dir": "golden/dev",
"runBy": "loop",
"minPass": 172,
"maxNegFp": 0,
"why": "調整に使ってよい組。毎周回す。合格ラインではなく、過去の決定が変わったら気づくための線"
}
176 問中 172 問以上、進行の誤りは 0。
変えれば git の差分に出ます。
今日の値(10/29) — golden master testing#
ACC 100.0% (176/176) REACH 100.0% (+f1 0) NEG_FP 0 NEG_F1 8 CHAT 18 LOCK_OK 12/12
THIN 12(1位2位の差 0.10 未満の採用: 正解 12 / 誤り 0)
1.64 秒です。
ループ(Scripts/adv-loop.ps1)の毎周に入っています。
凍結の履歴#
「凍結後は編集禁止」と注記にありますが、git の履歴を見ると 5 回変わっています。
中身を全部確かめました。
- 2026-08-03 2bde898d … 新規。dev 142 問・holdout 52 問。照合器を書く前の commit です。
- 2026-08-03 436943b6 … 照合器と同じ commit で dev 172・holdout 60 に組み直し、ここで凍結。
- 2026-08-11 9ad8f87c … 4 問を追加。実際に遊んでもらって、佐伯の「古い話」を自分の言葉で聞くと段が開かなかった件です。うち 1 問は、何も知らない段階で名前の出る段を開けてはいけない、という番人です。
- 2026-09-06 6ce7f3f9 … 28 行を書き換え。段の番号(#0)を段の名前(#alibi_s0)に置き換えただけで、指している段は同じです。
- 2026-09-22 1740efb9 … 注記 1 行の言葉を直しただけ。
期待の値を「いま通る値」に書き換えた差分は、ありませんでした。
名前どおりでない所 — golden master testing#
一つ目が、この回の題です。
うちの期待は、出力を写したものではありません。
最初の 142 問は、照合器がまだ無い commit で、人が「この入力ならこの意図」と書いたものです。
characterization test の手順 2(出た値を保存する)を、うちは踏んでいません。
同じ日の組み直しで出力を見ながら足した問いが無かったとは、差分からは言えません。
それでも、期待の書き方は最後まで「出力」ではなく「正解の意図 id」でした。
これは大きな違いです。
本当の golden master なら、今の出力にある誤りも正解として固定されます。
うちの probe-critical(進行に要る入力 244 問)を例にすると、今日は 27 問が落ちています。
これを golden master の作り方で固定すれば、27 問の誤った着地が「正解」になり、全部緑になります。
期待が正解の id なので、うちでは 27 問が落ちたまま見えています。
だから正確には、うちの dev は「人が正解を付けて凍結した回帰テストの組」です。
機械学習の言葉なら、凍結した評価用の組に近い。
golden master と呼んでいるのは、凍結して毎回同じ物差しで測る、という運用の側が似ているからです。
二つ目は、名前の対応表(named-methods-audit)がこの行を △ にしている理由です。
dev と組で作った holdout(見ない組)の使い方が、名前どおりではありませんでした。
これは T42 で書きます。
三つ目、approval の手順(差分を人が見て承認し、基準を更新する)を持っていません。
dev の期待は追加と名前の置き換え以外で変えない、という運用だけです。
道具で止めているのではなく、注記と習慣で止めています。
近いものは別にある#
うちで本当に「前の版を写して、差を見る」のは、別の道具です。
Scripts/adv-authoring-guard.mjs は、台本の文章を書き足すときに、git の前の版と今の版を比べます。
既存の台詞を一字でも変えた、既存の地の文を消した、を止めます。
冒頭のコメントにこうあります。
// lint と守備範囲が違う。lint はいまのデータだけを見る。ここは「前との差」を見る。
// 消えた台詞は、消えたあとのデータをいくら見ても分からない。
こちらは正解を持たず、前の版を基準にしています。
characterization test の考え方に近いのはこちらです。
弱点と、採らなかった案 — golden master testing#
- 176 問は全部第一章です。第二章以降には、凍結した組がありません。各章の通し台本の !expect(T37)が代わりをしています。
- 問いは 8 月に書いたものが中心で、10 月に増えた意図や言い回しは入っていません。dev が 100% でも、新しい意図の取り違えは見えません。それは進行に要る入力の組(probe-critical など)が見ます。
- 1 問 1 回の照合なので、続けて入力したときの文脈(直前の話題、表示済みの文の参照)は測れません。
- minPass 172 は「4 問までは落ちてよい」です。100% を毎周要求すると、意図的な改善で 1 問動いただけで止まるからです。ただ、4 問分の余裕は、黙って 4 問壊れても緑ということでもあります。
採らなかった案です。
- 出力を丸ごと保存する本当の golden master(応答の台詞まで含めて写す)。台詞を 1 字直すたびに全部の基準を取り直すことになり、取り直すたびに中身を読まずに承認する誘惑が来ます。照合の正しさを見たいので、期待は着地の id だけにしました。
- dev を毎回作り直す。作り直すと、過去の判断が変わったことに気づけなくなります。凍結が目的です。
- 問いを照合の言い換え辞書から機械的に作る。辞書と同じ文で測っても、辞書を確かめているだけです。凍結した問いは、辞書の文と字面が近すぎないかを lint が見ています(L4)。
検索語#
golden master testing
characterization test Feathers
approval testing
Guru Checks Output
「アリバイは」の一言でも、佐伯は答えます。言い方をいろいろ変えて、どこまで届くか試してみてください。『霧走町 流氷に眠る』の第一章をブラウザで遊べます。プロフィールのリンクから。