kaedeasset記事 · Notes霧走町 流氷に眠る

凍結した 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・技術・日本語入力でプレイする仕組み

確かめ方golden master testingcharacterization testingapproval testingregression testingclaude自然言語処理

港で佐伯に「アリバイは」とだけ入力したら、アリバイの証言に着地する。
こういう入力と正解の組を 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 はここで発想を逆にしました。

「正しいかどうかは置いておく。いま実際にこう動いている、を記録する」

手順はこうです。

  1. いまのコードに入力を流す。
  2. 出た出力をそのまま保存する(これが golden master)。
  3. コードを変えたら、同じ入力を流し、保存した出力と一致するかを見る。
  4. 違えば、人が見て「意図した変更か」を決める。意図したなら保存し直す。

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

「アリバイは」の一言でも、佐伯は答えます。言い方をいろいろ変えて、どこまで届くか試してみてください。『霧走町 流氷に眠る』の第一章をブラウザで遊べます。プロフィールのリンクから。

#claude #自然言語処理