こんにちは。小学生向けのニュースサイト、こどもニュースをつくっています。
このサイトは静的書き出しで、ビルドすると HTML と一緒に XML も出ます。sitemap、News sitemap、RSS、Atom の 4 つです。
これらが壊れても、サイトは普通に見えます。壊れたことに気づくのは、検索コンソールで警告が出たときか、フィードリーダーで読めないと言われたときです。どちらも数日から数週間の遅れがあります。
そこで、ビルド成果物の XML を検証する工程を入れました。
何で壊れるか
XML が壊れる原因は、だいたい 2 つです。
1 つは、データに含まれる特殊文字です。記事のタイトルに & が入ると、エスケープしていなければその時点で整形式でなくなります。日本語のタイトルでも & は普通に出てきます。
もう 1 つは、テンプレートの編集ミスです。タグの閉じ忘れ、属性のクォート漏れです。手で組み立てる文字列なので、型では守れません。
どちらも「たいていのデータでは壊れない」のが厄介です。テストデータには & が入っていないので、テストは通ります。本番のデータで初めて壊れます。
xmllint を使う
XML の検証は、専用のツールに任せるのがいちばん簡単です。多くの Linux 環境で使える xmllint を使いました。
xmllint --noout --nonet <file>
--noout は結果を出力しない(検証だけ行う)、--nonet はネットワークへ取りに行かない指定です。
2 つ目が重要でした。XML には外部の DTD やスキーマを参照する仕組みがあり、これを取りに行くと、CI がネットワークに依存します。取りに行った先が落ちていると、こちらの検証も失敗します。整形式かどうかを見るだけなら、外部参照は不要です。
検証するのはビルド成果物
検証の対象は、実際に出力されたファイルです。生成する関数の単体テストではありません。
理由は、単体テストだとテストデータでしか検証できないからです。本番のデータで & が入っていることを、テストデータは知りません。
このサイトでは E2E がビルドを行うので、その成果物に対して検証を掛けています。E2E が失敗したときは、成果物が無いか古いので、検証はスキップします。前の工程の結果に依存する検査は、依存関係を明示しておかないと、古いファイルを検証して通ってしまいます。
環境への依存をどうするか
xmllint は外部のコマンドなので、実行環境に入っている必要があります。Node のパッケージで完結させたほうが移植性は高いです。
それでも外部コマンドにしたのは、この検査が「あれば良い」ものだからです。入っていない環境ではスキップされても、他の検証は動きます。そのために、セットアップの手順に 1 行書いておきました。
sudo apt-get install -y --no-install-recommends libxml2-utils
個人開発では、環境が 1〜2 台に限られます。移植性より、その環境で確実に動くことのほうが優先度が高い場面がありました。
整形式より先はやっていない
この検証で見ているのは「整形式かどうか」だけです。sitemap の仕様に沿っているか、必須の要素が揃っているかは見ていません。
スキーマによる検証もできますが、そこまではやっていません。理由は、仕様違反は検索コンソール側でも検出されるからです。一方、整形式でない XML は、そもそも読まれずに終わります。被害の大きさが違います。
検査を足すときは、「これで何を防げるか」を 1 つに絞ると、実装も判断も軽くなります。全部を防ごうとすると、たいてい何も入りません。
同じ形の検査が他にもある
このプロジェクトの pre-commit には、似た性格の検査が並んでいます。
- ドキュメントが挙げるファイルパスが実在するか
- 宣言していないパッケージを import していないか
- スキーマ定義の 3 者で列名が揃っているか
どれも「壊れても lint もテストも通る」領域です。共通しているのは、壊れたことに気づくのが遅れることでした。
自分のプロジェクトで「気づくのが遅れる壊れ方」を探すと、だいたいこういう場所が見つかります。成果物、ドキュメント、設定です。コードのようにテストが当たらないものほど、静かに壊れます。
まとめ
- 生成した XML は、データに含まれる特殊文字とテンプレートの編集ミスで壊れる
- どちらも「たいていのデータでは壊れない」ので、テストデータでは検出できない
- 検証はビルド成果物に対して行う。
xmllint --noout --nonetで足りる - 外部参照を取りに行かせない。CI がネットワークに依存する
- 前の工程の成果物を検証する場合は、依存関係を明示してスキップさせる
- 検査は「何を防ぐか」を 1 つに絞る。整形式かどうかだけでも価値がある