← 新しい形の研究 A-7 / 自動テストの実際

自動テストの実際 — 何を機械に見張らせるか

テストする対象が、
変わってしまった

関数の戻り値は、もう主戦場ではありません。壊れるのは 音・レイアウト・読めない言語・公開物で、しかもエラーを出しません。 実際に事故を止めた検査を、コードごと全部書き出します。

2026-08-24 / 16作品の制作から / 読了 約10分

01 — 制約

テストは「計算の正しさ」を見るものだった

従来の自動テストは、入力に対する出力を確かめるものでした。関数を呼び、期待値と比べる。 これは正解が1つに決まる領域ではよく機能します。

ところが個人がAIと作るものは、ほとんどが正解が1つに決まらない領域です。 「この音声は正しく読まれているか」「このレイアウトは重なっていないか」 「7言語のうち韓国語だけ壊れていないか」。従来の型では書けません。

そして最悪なのは、これらが失敗しても赤くならないことです。
テストが無いのではなく、テストできる形に落ちていなかった

02 — 解除

判定を機械が持てるようになった

変わったのは2つです。ヘッドレスブラウザが安価に回せるようになったこと、 そして音声や画像を機械が判定できるようになったこと。 つまり「人が見ないと分からない」とされていた対象が、次々と数値に落ちるようになりました。

見たいもの以前いま
音声が正しく読まれたか耳で聴く書き起こして音で照合
要素が重なっていないか目で見る座標を取って断言する
公開物が正しいかブラウザで開くライブURLを叩いてハッシュ照合
翻訳が抜けていないか読める人に頼む件数と文字種で判定
03 — 新形式

実際に事故を止めた、七つの検査

① 棚卸し — 両方向で数える

生成した資産は、参照と実体を両方向で突き合わせます。片方向だと半分見逃します。

参照=237  実ファイル=237  欠落=0  孤児=0  破損疑い(<800B)=0

実績: 前作で161本の音声が無言のまま数週間動いていたのを、この検査で発見しました。

② 座標で断言する — 見た目も自動で見る

二言語同時字幕を入れたとき、「重なっていないこと」を目視で確認するのは毎回できません。 要素の矩形を取って、断言に変えます

const ok = pg.evaluate(() => {
  const a = document.querySelector('#tx').getBoundingClientRect();
  const b = document.querySelector('#tx2').getBoundingClientRect();
  return b.top >= a.bottom - 1;      // 2つ目が1つ目を侵していない
});

同じ考え方で横スクロールの発生も一行で見られます。これはモバイル崩れの最頻出パターンです。

document.documentElement.scrollWidth > window.innerWidth + 2   // true なら崩れている

③ 実物を検証する — 作業コピーを信用しない

いちばん痛かった失敗です。多言語化で台本のデプロイを忘れたとき、 フォールバックが働いて全言語が完璧な日本語で表示されました。 ローカルのテストは全部通ります。手元には正しいファイルがあるからです。

# 公開されている実物と、手元が一致しているか
LOCAL=$(md5sum assets/voice/k2102619210.mp3 | cut -d' ' -f1)
curl -s https://example.com/assets/voice/k2102619210.mp3 | md5sum

反映待ちのポーリングは、対象に固有の文字列で行うこと。 ここでも一度失敗しました。「AIでしか作れない」で待っていたら、それは旧版にも含まれる語で、 反映前に反映済みと誤判定しました。待ち条件はid="newforms"のように新版だけが持つものにします。

④ スタブで隔離する — テストが本番を汚さないように

クリア率の計測を入れたとき、テストのたびに本番の集計へ数字が入る状態になっていました。 外部への送信は遮断し、呼び出しだけ観測します。

pg.route("**gc.zgo.at/**", r => r.abort());          // 本物へ送らせない
pg.add_init_script(`window.__ev=[];
  window.goatcounter={count:o=>window.__ev.push(o.path)};`);
// …操作したあと
assert "game/hyaku/clear" in pg.evaluate("window.__ev")

遮断を忘れると、本物のスクリプトが読み込まれてスタブを上書きし、観測が空になります。 実際にそれで一度、原因を見誤りました。

⑤ 音は音で照合する

書き起こしは音を漢字に戻すので、読みの間違いは文字比較では原理的に検出できません。 漢字トークンを禁止して、仮名で出させます。

kanji = re.compile(r'[一-鿿]')
suppress = [-1] + [i for i in range(tok.get_vocab_size()) if kanji.search(tok.decode([i]))]
model.transcribe(path, language="ja", suppress_tokens=suppress,
                 initial_prompt="ひらがなだけでかきます。")

⑥ 多数決で偽陽性を潰す

自動判定は間違えます。長文で仮名を強制すると、デコードが破綻して 一致率0.09のような異常値を出すことがある。音声そのものは正常なのにです。

切り分けは、条件を変えて3回聞き、多数決を取る。それと物理量の確認です。

尺 24.0s / 想定 21.7s → 比 1.11   # 音声は存在する
音量プロファイル: # # # # # # . # # #  # 途中で無音になっていない
→ 一致率の異常は、書き起こし側の破綻と判断

テストが失敗を報告したとき、テスト自身を疑う手順を持っておくのが実務では重要です。

⑦ タグ収支 — 構造を壊さずに書き換える

大きなHTMLのブロック移動は、閉じタグを1つ落とすだけでページが崩壊します。 書き込むに収支を数えて、合わなければ書き込まない。

before = counts(src)          # div/section/a/p の開き・閉じ
src = 変換(src)
after  = counts(src)
assert all(after[t][0] == after[t][1] for t in after), "タグ収支が合わない"
open(path, "w").write(src)    # 通ったときだけ書く

実績: 過去に閉じタグ2つを落としてページ幅が7,972pxに爆発した事故があり、以来この順序を守っています。

04 — 運用

回し方のほうが、テストの中身より効く

完了を「観測」で定義しておく

着手前に、何をもって終わりとするかをコマンドの出力で決めます。主観の「できました」を許さない。

工程完了の観測
台本実プレイで最終話に到達できる
音声欠落0 / 孤児0 + 読み検証PASS
多言語各言語の要素数が一致(238件)
公開ライブURLを叩いて期待の中身が返る

重い検査は、走らせる場所を選ぶ

全部を毎回回すと、走らせなくなります。安いものを常に、高いものを条件付きで。

環境の落とし穴(実際に踏んだもの)

症状原因抜け方
スクリプトがJS構文エラーで動かない シェル→Python→JSとエスケープを三重に通した 正規表現をJS側に書かない。innerTextだけ取ってPythonで解析する
ヒアドキュメントでファイルが壊れる \b\tがエスケープとして解釈された 大きな差し込みは別ファイルに書いてから読み込む
ブラウザ自動化が接続できない 既定プロファイルではデバッグポートが開かない/既に起動中だとフラグが無視される 自動化専用プロファイルを分ける。普段のブラウザを閉じずに済む
選択肢のクリックが効かない アニメーション中で操作可能判定に入らない JSの.click()で直接呼ぶ
05 — 再現

最初に入れるなら、この3つ

  1. タグ収支の断言 — 5分で書けて、ページ崩壊を確実に防ぐ
  2. JSエラー0 と 横スクロール無し — ヘッドレスで全ページを巡回するだけ。最も費用対効果が高い
  3. ライブ実物の検証 — 「出したつもり」を捕まえる唯一の方法

この3つを回すだけの巡回テストは、実際こう出ます。

/index.html                        err=0 PC溢=False SP溢=False
/method/                           err=0 PC溢=False SP溢=False
/method/hinshitsu-kenmon.html      err=0 PC溢=False SP溢=False
内部リンク切れ: なし
判定: ALL OK

自動テストの目的は、正しさの証明ではありません。「気づかないまま出す」ことを防ぐことです。
だから見張る対象は、コードではなく読者が実際に触れるものに置きます。

静かに壊れる不具合の分類は 品質検問、 作る前の工程は 質問の解像度 に書いています。