コンテンツにスキップ
getnextpdf.com

ゴールデンファイルテスト

Spec: ISO/IEC/IEEE 29119-4Spec: ISO/IEC 25010

ゴールデンファイルとは、「正しい出力とはこういうものだ」と記録したものであり、テストは実行のたびにこれと照合します。NextPDF はゴールデンを使って、誰も意図しなかった変更を検出します。圧縮方法が変わったストリーム、位置が変わった段落、ずれた座標などです。このページでは、その仕組みと、ゴールデンを誰も読まない古い参照にせず、信頼できる状態に保つ方法を説明します。

PDF の生成は長いパイプラインであり、知らないうちにドリフトし得る箇所が数多くあります。「何も変えていないはず」のリファクタリングが、いつの間にかオペレーターの順序を入れ替えたり、変換行列を変更したり、テーブルのセルをわずかにずらしたりすることがあります。ユニットテストでは、こうした変化をほとんど捕捉できません。確認しようと考えた値は検証しますが、想定しなかった何千ものバイトは検証しないからです。構造ベースの技法と仕様ベースの技法はそれぞれ異なる種類のエラーを検出し、一方が他方を包含することはありません(ISO/IEC/IEEE 29119-4, Annex A)。ゴールデンファイルは、1 つのアサーションではなく出力全体をピン留めする、例による仕様です。

リスクは双方向にあります。厳格すぎるゴールデンは、無害な変更のたびに失敗し、何も証明しなくなるまで盲目的に再承認されてしまいます。緩すぎるゴールデンは、本物のリグレッションを見逃してしまいます。そのバランスを正しく取ることこそが、この技術の要です。

  • ゴールデンファイルとは、既知の正常なエンジン動作から生成され、リポジトリにコミットされた、ピン留めされた参照出力です。
  • ゴールデンテストは出力を再生成し、ピン留めされた参照との差分を取ります。差分があればテストは失敗し、人間による判断が求められます。
  • NextPDF は意味があり、かつ安定したレベルで比較します。生のバイトではなく、抽出されたテキストと正規化された構造オペレーターです。なぜなら、生のバイトにはリグレッションではないノイズ(タイムスタンプ、サブセットの順序、圧縮)が含まれるからです。
  • ゴールデンの更新は、明示的な GOLDEN_UPDATE スイッチでのみ行われる、意図的でレビュー済みの操作です。変更されたものを何でも自動的に「受け入れる」ことは決してありません。
  • ゴールデンは、スナップショットテストやキャラクタリゼーションテストとは決定的な点で異なります。ゴールデンは決して自動更新されないという点です。

エンジンのゴールデンテスト基盤は、バイト比較ではなく、明示的な2 層の差分を使用します。

  1. GenerateRender the fixture input through the current engine.
  2. Layer 1 — textExtract human-readable text from the content stream; diff against the text golden. Catches dropped or reordered content and encoding regressions.
  3. Layer 2 — structureExtract ordered PDF operators, normalise coordinates to a fixed precision, diff against the operator golden. Catches layout shifts and broken structure.
  4. DecideAny diff fails the test; a human judges whether it is a regression or an intended change.
NextPDF のゴールデン比較の流れ: PDF を生成し、意味のある層(テキスト、次に正規化された構造オペレーター)を抽出し、それぞれをピン留めされた参照と差分を取り、差分があれば人間が読めるレポートとともに失敗します。

あえて比較しないものは、比較するものと同じくらい重要です。生のバイト出力は除外されます。タイムスタンプ、フォントサブセットの順序、ストリームの圧縮が、正しさへの信頼を高めることなくテストを脆くしてしまうからです。ピクセル単位の画像差分は、外部レンダラーを必要とし、環境のばらつきを取り込んでしまうため、この層からは除外されています。浮動小数点の座標は固定精度に正規化されるため、無意味な丸めのノイズがリグレッションを装うことはありません。これが、動作をテストするゴールデンと、一時的な環境ノイズをテストするゴールデンとの違いです。

この選択は、このページの再現性プロファイルにも表れています。つまり構造的(structural)です。NextPDF は 3 つのプロファイルを文書化しています。bitwise(正確なバイトが再現される)、structural(オブジェクトグラフとオペレーター列が再現され、無害なバイトレベルのばらつきは許容される)、semantic(意味が再現される)です。この層のゴールデンテストは、設計上、構造的プロファイルを検証します。だからこそ、これらの参照は圧縮ライブラリのバージョンアップには耐えても、テーブルの移動には依然として失敗するのです。

ゴールデンテストは仕組みとしては単純であり、規律が求められるのはその周囲のワークフローです。

<?php
declare(strict_types=1);
// 1. The fixture: a fixed HTML input committed next to the test.
// tests/Golden/fixtures/html-inputs/002-basic-table.html
// 2. The pinned references, generated once from known-good behaviour:
// 002-basic-table.text.golden (Layer 1 — extracted text)
// 002-basic-table.operators.golden (Layer 2 — normalised operators)
// 3. The run compares; ANY difference fails:
// vendor/bin/phpunit --testsuite Golden
// 4. An intended behaviour change is the ONLY time references move,
// and it is explicit and reviewed — never automatic:
// GOLDEN_UPDATE=1 vendor/bin/phpunit --testsuite Golden
//
// The regenerated *.golden files land in the diff of the same change
// that altered behaviour, so a reviewer sees the output delta next to
// the code delta and signs off on both together.

この例が示しているのは、プロセスそのものです。テストコードは差分を取るだけです。ゴールデンを信頼できるものにしているのは、変更された参照が、エンジン変更と同じ変更の中で出力としてレビューされる点です。

最もよくある誤りは、ゴールデンテストをバイト単位のテストとして扱うことです。NextPDF のゴールデンは、ファイルのバイトそのものではありません。抽出されたテキストと、正規化された構造オペレーターです。生のバイトを検証すると、新しい zlib のバージョン、異なるサブセットタグ、再生成されたタイムスタンプで失敗してしまいますが、いずれもリグレッションではありません。そしてそのテストは、1 週間のうちに再承認を繰り返され、役に立たなくなってしまうでしょう。(正確なバイトを本当に再現しなければならない場合、それはゴールデンではなく、別個でより厳格な bitwise 再現性プロファイルです。)

2 つ目の誤りは、ゴールデンスイートがグリーンであれば正しさが証明されると思い込むことです。証明するのは変化していないことです。バグのある出力から生成されたゴールデンは、そのバグを忠実に固定してしまいます。ゴールデンは、既知の正常なベースラインからのリグレッションを防ぎますが、そのベースラインが正しかったことを立証するものではありません。それこそが、ユニット、構造、適合性の各層の役割です。

ゴールデンテストが答える問いは、ただ 1 つです。すなわち、出力がピン留めされた参照から変化したかどうかです。参照がそもそも正しかったかどうかについては、何も語りません。また、パフォーマンス、適合性、セキュリティを測定することもありません。それらは別の層の役割です。フィクスチャコーパスのサイズ、スイートの合格率、各種カバレッジ数値は、継続的インテグレーションのアーティファクトから生成され、ビルドとともに公開される生きた品質シグナルです。それらは陳腐化するおそれがあるため、ここにはあえて記載していません。

正確なディレクトリレイアウト、コンパレーターの内部、更新スイッチは、エンジンのテスト基盤が所有しており、変化する可能性があります。この説明と食い違う場合は、テスト構成が信頼すべき根拠となります。このページは、他のライブラリのスナップショットやゴールデンのツールについて、いかなる主張も行いません。

  • ゴールデンファイル — 既知の正常なエンジン動作から生成されてコミットされた、ピン留めされた参照出力。テストが実行のたびにこれと差分を取ります。決して自動更新されません。
  • 2 層の差分 — NextPDF のゴールデン比較。生のバイトの代わりに、抽出されたテキスト(Layer 1)と正規化された構造オペレーター(Layer 2)を用います。
  • スナップショットテスト — 関連はあるが別個の技法。参照が初回実行時に再生成され、ドリフトは更新フラグを通じて受け入れられます。
  • キャラクタリゼーションテスト — 既存の動作が正しいと主張することなくそれを固定するテスト。通常、リファクタリングを安全にするために用いられます。
  • 再現性プロファイル — 出力が再現されなければならないレベル。bitwise(正確なバイト)、structural(オブジェクトグラフとオペレーター列。無害なバイトのばらつきは許容)、semantic(意味)のいずれか。このページで扱うゴールデンテストは、構造的プロファイルを検証します。
  • GOLDEN_UPDATE — ゴールデン参照の上書きを許可する、明示的な環境スイッチ。まれで、レビューされる操作です。