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

どの台本を、誰が、どの合格ラインで回すか。検査の目録を道具から作品へ移した

Which script, run by whom, with which pass line: moving the test catalog from the tool to the work

 D23-4-T46-declarative-test-manifest・技術・日本語入力でプレイする仕組み

確かめ方data-driven testingdeclarative test manifesttest inventoryratchet(上限を下げるだけの線)claude自然言語処理

霧走町の回帰テストは、作品フォルダの中の 46 本の台本と、1 つの目録(runs/suite.json)で決まります。
検査を回す道具は、台本の名前も合格ラインも一つも持たず、目録に書かれたとおりに回して、戻り値だけを見ます。

T37 では台本の中の期待(!expect)、T38 では凍結した問い、T42 では見ない組(holdout)を書きました。
今日は、それを全部束ねている目録の話です。

この回の位置と、前後の回#

区切り: T34〜T49 確かめ方(16 回のうち 13 回目)
先に読むとよい回: T37(台本の中に期待を書く)・T38(今の出力を写して固定する)・T42(holdout を見た回数で数える)
次に読む回: T47(合格の証拠を測った版に縛る)

名前 — data-driven testing#

正式名
data-driven testing(データ駆動テスト)・declarative test manifest(宣言的な検査の目録)
略称
DDT(data-driven testing をこう略す例がある)
日本語での呼び名
データ駆動テスト、テストの目録
初出
どちらの語も、初出は確かめられませんでした。
近い名前
keyword-driven testing(手順の語彙を表に書く)・test inventory(検査の棚卸し)

一般の解説 — data-driven testing#

data-driven testing(データ駆動テスト)とは、検査の手順と検査に使うデータ(入力と期待)を分け、1 本の手順をデータの行ごとに回すテストの書き方です。

  • data-driven testing

検査の手順(ロジック)と、検査に使うデータ(入力と期待)を分けます。
手順は 1 本だけ書き、データは表やファイルに並べて、行ごとに同じ手順を回す。
入力の組み合わせが多い検査(境界値・言語ごとの文字列・料金の表など)で、コードを増やさずに検査を増やせます。
テストの枠組みには、たいていこの口があります(パラメータ化されたテスト、外部の表を読む仕組み)。

  • declarative test manifest

もう一段上の分け方です。
どの検査を、どの順に、どの条件で、どの合格ラインで回すかを、コードではなくデータ(目録)に書きます。
道具は目録を読むだけで、どの検査が存在するかを知りません。
コンパイラの試験の仕組みのように、試験のファイルの中に「どう走らせるか」を書いておき、道具が拾って回す形もあります。

よく知られた利点:

  • 検査を足すのに道具を書き換えなくてよい
  • 合格ラインが目録の diff に出る(緩めたら分かる)
  • 目録を対象と同じ場所に置けば、対象を足した人が目録も直す

よく知られた弱点:

  • 目録と実物がずれる(目録に載っていない検査は回らない・目録にあるのに実物が無い)
  • データにした分だけ、型の検査が効かない。綴りの誤りが黙って通る
  • 表に書けない期待(作品ごとの細かい条件)が、結局コードに戻る

移す前に起きていたこと#

2026-08-17 まで、台本は Scripts/ の中に、作品の名前を接頭辞にしたファイル名で置いていました(adv-<作品>-*.txt)。
「どの台本を誰が回すか」は、検証の道具の中のハードコードした表にありました。
目録の頭の注記に、こう残っています。

"なぜここに置くか: 2026-08-17 まで、台本は Scripts/adv-<作品>-*.txt と",
"ファイル名だけで区別され、『どれを誰が回すか』の目録は Scripts/adv-verify-aux.ps1 の",
"ハードコードした表にあった。作品が増えるたびに表が腐り、実際 adv-misaki-grounds は",
"表に載らないまま adv-loop.ps1 が回していた(=棚卸しが FAIL するのに誰も見ていない)。",
"目録を作品フォルダに置けば、台本を足した人が同じフォルダで目録も直す。",

一般の解説の弱点の 1 つ目を、そのまま踏んでいました。
作品が 2 本になったとき、作品ごとの検証スイートもファイルごと複製されていました。

いまの置き場の規約#

規約はこう決めました(アーキテクチャの文書 §7.0)。

Scripts/ に作品名を書かない。作品に属するものは Game/adv-<work>/ の中だけ。
区別はフォルダでつける。ファイル名の接頭辞(adv-ch1-…)では区別しない。

場所 何が入るか
Game/adv-<work>/runs/*.txt 通しの台本・封印の問い(adv-sim play --cmds の入力の列)
Game/adv-<work>/runs/suite.json どの台本を誰がどの合格ラインで回すかの宣言
Game/adv-<work>/checks/*.ps1 その作品だけの期待を持つ検査
Scripts/ 汎用の道具だけ。作品は -Work <dir> で受け取る

規約の狙いは 2 つだけ、と書いてあります。別の作品を同じ道具で測れること。合格ラインを黙って動かせないこと(緩めたら git diff に出る)。

目録の形#

目録の行は、台本 1 本ごとです。第一章の行を 2 本写します。

{
	"id": "probe-sealed2",
	"cmds": "runs/probe-sealed2.txt",
	"runBy": "loop",
	"why": "受け入れ条件2(雑談の下限保証)。**この文を言い換え辞書に写さない**(lint L18 が検出)",
	"gates": ["--min-answered", "60", "--max-leak", "0", "--max-miss-run", "2"]
},
{
	"id": "misaki-grounds",
	"cmds": "runs/misaki-grounds.txt",
	"runBy": "loop",
	"why": "岬の証言は根拠を持ってから出る(08/11 実プレイ)。旧目録に載っていなかった1本",
	"gates": ["--max-leak", "0", "--max-miss-run", "0"]
}

(表示のため、gates の配列は 1 行にまとめました。)

欄 意味
cmds 台本のパス(作品フォルダからの相対)
runBy 誰が回すか。loop(汎用の回帰テストが回す)・check(作品の checks/*.ps1 が回す)・selfplay(1 手ごとに別のプロセスで遊ぶ自己プレイ)・ue(UE 実機)・not-a-test(判定を持たないと決めたもの)
gates 合格ライン。adv-sim play にそのまま渡す引数の列
why なぜこの台本があるか(テスト計画のどの条件に当たるか)
route 攻略の地図に描く順路のときだけ

霧走町の目録の gates に出る選択肢は 4 つです。--require-clear(章を終えること)・--max-leak(話題の語が無いのにフラグが立った手 LEAK の上限)・--max-miss-run(受け止めなかった手 MISS が続く手数の上限)・--min-answered(ANSWERED の割合の下限)。1 手ずつの 4 値の付け方は T41 で書きました。

目録には、台本の行のほかに、作品全体の欄もあります。

  • golden: 凍結した問いの組ごとの回し方(dev は毎周、holdout は節目だけ)と下限(T38・T42)
  • ceilings: 警告の上限。増えたら FAIL、減らしたら同じ commit で下げる(ラチェット)
  • checks: その作品だけの検査のスクリプトの一覧
  • acceptance: 後のレベルの台帳(T47)

道具の側#

回帰テストの道具(Scripts/adv-loop.ps1)は、台本の名前を一つも持ちません(写す)。

# **どの台本をどの合格ラインで回すかは作品の宣言(suite.json の runBy: loop と gates)。**
# ここに台本名を書かない——書くと作品が増えるたびにこのファイルが伸び、片方だけ腐る。
$loopRuns = Get-AdvRuns $Suite 'loop'
if ($loopRuns.Count -eq 0) { Check 'suite' $false "runBy=loop の台本が1本も無い: $Work/runs/suite.json" }
foreach ($r in $loopRuns) {
    PlayCheck $r.id $r.cmds @($r.gates)
}

PlayCheck は、gates をそのまま adv-sim play の後ろに付けて流し、合否は戻り値だけで決めます。

$out = Invoke-AdvExe { & $Exe @(@('play','--data',$Data,'--cmds',$f,'--quiet','--stats-out',$sp) + $ExtraArgs) 2>&1 } ([ref]$rc)
# **判定は戻り値だけ。** 出力の PLAY VERDICT 行は人が読むためのもので、判定には使わない。
$ok = ($rc -eq 0)

数は表示の行から拾わず、--stats-out の JSON で受けます。JSON が空なら「数え損ね」として赤にします。0 と読まないためです。
gates の無い loop の台本(今日 10 本)は、台本の中の !expect が外れれば play が 0 以外で終わるので、それが合否になります(T37)。

一般の解説の弱点の 1 つ目(目録と実物のずれ)には、棚卸しで答えています。

# 棚卸し: runs/ にあって suite.json に載っていない台本を返す。
# **新しく置いた台本が誰にも回されないまま溜まるのを止める歯止め。**
function Get-AdvUnlistedRuns([string]$Work, $Suite) {
    $dir = Join-Path $Work 'runs'
    if (-not (Test-Path $dir)) { return @() }
    $listed = @{}
    if ($Suite) {
        foreach ($r in $Suite.runs) { $listed[[IO.Path]::GetFileName($r.cmds)] = $true }
    }
    $out = @()
    foreach ($f in Get-ChildItem $dir -Filter '*.txt' -File) {
        if (-not $listed.ContainsKey($f.Name)) { $out += $f.Name }
    }
    return $out
}

runs/ に置いたのに目録に無い台本があれば、回帰テストの inventory の項目が FAIL になります。撮影用の台本のように判定を持たないものも、not-a-test として目録に載せ、why に理由を書きます。載せないと落ちるので、「判定しない」も宣言になります。
逆向き(目録にあるのに台本が無い)は、loop の行なら PlayCheck が「台本が無い」で赤にします。ほかの runBy の行は、それを回す道具の側の仕事です。

今日の数#

霧走町の目録は、台本 46 本です。

runBy 本数
loop 27(うち gates つき 17)
check 10
not-a-test 6
ue 2
selfplay 1

runs/ の .txt も 46 本で、目録に載っていない台本は 0 でした。
gates に出る選択肢の回数は、--max-leak 16・--max-miss-run 14・--min-answered 5・--require-clear 3 です。

同じ形の目録を持つ作品フォルダは、霧走町のほかに 15 あります。同じ道具に -Work で渡せば、その作品の目録で回ります。

2026-08-18 の是正: 受け取りながら無視していた#

規約を書いた翌日の棚卸しで、作品を固定で見ている道具が 5 本見つかりました。うち 3 本は、-Work を受け取りながら一部で無視していました。
たとえば、AI のプレイヤーが 1 手ずつ遊ぶための道具(adv-agent.ps1)は -Work を取るのに、焼いたベクトルを霧走町のフォルダから読んでいて、別の作品でも霧走町のベクトルで照合していました。
アーキテクチャの文書の教訓は、こうです。

「-Work を取るか」では独立を測れない。渡した値が最下段まで届くかで測る。
確かめ方は、別作品で回して数字が変わること(章1と同じ数字が出たら届いていない)。

指定が黙って効かないのは、固定より質が悪い、とも書いてあります。

名前どおりでない所 — data-driven testing#

目録と台本の分け方は、名前どおりです。台本(入力の列と期待)がデータ、目録がその束ね方と合格ライン、道具は目録を読むだけです。
違いは二つです。

一つ目、目録が「誰が回すか(runBy)」まで持っています。一般の目録は、ふつう一つの道具のためのものです。うちの目録は、回帰テスト・作品の検査・UE 実機・自己プレイの 4 つの道具の束ね先で、道具ごとに自分の行だけを読みます。
二つ目、棚卸しがあります。目録に無いものを落とすのは、目録と実物のずれを止めるための足し物です。

規約との差も二つあります。

  • 規約は「Scripts/ の検証ランナーに作品名を書かない(起動用スクリプトの既定引数は可)」ですが、検証ランナーの回帰テスト(adv-loop.ps1)・判定(adv-judge.ps1)・テストのサマリ(adv-test-summary.ps1)などが、-Work の既定値に霧走町のフォルダを持っています。数えると、Scripts/ の .ps1 のうち 23 本でした。別の作品は -Work で渡せば測れるので、狙いの 1 つ目は満たしていますが、規約の文字どおりではありません
  • 霧走町の目録の goal の欄は、テスト計画が正になった後も、前のゴールの文書を指しています。ledger の欄は、存在しない台帳を指しています(T45)

どちらも、今日は直さず事実だけを書いておきます。

弱点と、採らなかった案 — data-driven testing#

  • 目録は JSON なので、型の検査が効きません。runBy の綴りを誤ると、どの道具もその行を読まず、黙って回らなくなります。棚卸しは「目録に載っているか」しか見ないので、これは見つけません
  • gates に書けない期待(意図の id の列・フラグの件数)は、作品の checks/*.ps1 に戻っています。目録の check の行は「期待は checks/verify.ps1」のように指すだけです
  • why は散文です。テスト計画のどの条件に当たるかは、人が読んで照らすしかありません

採らなかった案:

  • 台本の頭に、回し方と合格ラインを書く(試験のファイルの中に書く形)。台本 1 本で完結しますが、作品全体の欄(凍結した問い・上限・後のレベルの台帳)の置き場が別に要り、結局目録ができる
  • 目録を道具の側に置いたまま、作品ごとの節に分ける。作品を足すたびに道具が伸び、片方だけ腐る、の元の形に戻る
  • 規約に反するものを全部洗い出して直す。アーキテクチャの文書は、「動かしても今の判断が 1 つも安全にならない」直しをやらない、と決めています。判断は「黙って動かしたら合否が変わるか」「それで何かが測れるようになるか」の 2 問だけです

検索語#

data-driven testing
declarative test manifest
keyword-driven testing
test inventory orphaned tests

台本に無い聞き方で、『霧走町 流氷に眠る』の第一章を解いてみてください。ブラウザで無料で遊べます。プロフィールのリンクから。

#claude #自然言語処理