ソースコード以外もとにかくテストする。もしくはカバレッジだけではダメだという話
あなたはプロジェクトのソースコードに対して適切にCIを回しているかもしれません。定期的にコードカバレッジの測定も行い、90%以上もしくは100%の数字を出しているかもしれません。
しかし果たしてそれで十分でしょうか?もしくはコードカバレッジだけにとらわれすぎていないでしょうか?
監視とは(システムに対する)継続的なテストである
、というのは筆者の尊敬する奥一穂氏の言葉ですが、その逆もしかりで 「テストとはプロジェクトに対する継続的な監視である」 ということも言えます。
その観点に立ってみると、プロジェクトのソースコード以外にもテストが必要なものがたくさんあることに気づくでしょう。以下に実際に筆者が自分のプロジェクトの中でソースコード以外にテストを書き、CIを回していたものを挙げてみます。
アプリケーション設定ファイルのテスト
開発中に本番用の設定ファイルを使うことはないため、本番用の設定ファイルにバグが入ってしまっても案外気づきづらいものです。しかし設定ファイルを壊してしまったりするとクリティカルな問題が発生することは目に見えています。それを防ぐために以下の様なテストが必要でしょう。
- 設定ファイルのシンタックスは正しいか(壊れていないか)
- 設定項目の抜けはないか
Amon2などはデフォルトのひな形に、設定ファイルのシンタックスチェックと開発用、テスト用、本番用の設定ファイルに、設定項目の過不足が無いかチェックするテストスクリプトが自動で作成されるようになっています。
DBのスキーママイグレーションのテスト
GitDDL、GitDDL::MigratorやSQL::Translator、MySQL::Diffなどを利用して、差分積み上げ式のマイグレーションから開放され、任意のリビジョン間でmigrationを動かせるようにしているかもしれませんし、素朴な差分積み上げ管理をしているかもしれません。
いずれにせよマイグレーションの本番適用には緊張感が走ります。少しでも安心を獲得するために以下のようなテストが必要でしょう。
- 本番に適用されているDBスキーマにからテスト対象リビジョンのスキーマにmigration出来るかどうか
- テスト対象リビジョンのスキーマから本番の現状に対してrollback可能か
- migration -> rollbackした場合にスキーマが最初の状態と一致しているか
テストの実行にあたっては、Test::mysqldやTest::PostgreSQLなどで実際のデータベースを起動し、動かしてみることが必要でしょう。
理想を言うと、現在の本番環境のデータ量に対して現実的な時間でALTERが返ってくるかどうかの確認などもできると良いんじゃないかと思いますが、そこまではやったことはありません。その辺りはソースレビューなどでカバーしていました。
DBパーティションのテスト
MySQL::Patition等を活用して、毎日バッチなどで本番にパーティションを設定していることがあるかと思います。しかしMySQLのパーティションにはいくつか成約があるため、切れるつもりのパーティションが切れないことがあります。例えば以下の様なものです。
- 外部キー制約が使われているテーブルに使えない
- パーティション対象以外のカラムにUNIQUEキーを貼れない
- テーブルに一つしかパーティションが無いときはDROPできない
これらは、コードレビューでは漏れる可能性が少なからずありますが、機械的なチェックは簡単です。Test::mysqldでDBスキーマを再現し、パーティション対象のテーブル全てに、CREATE PARTITION
やDROP PARTITION
、REORGANIZE PARTITION
等を発行して正しく実行できるかどうかのテストをすれば良いでしょう。
自動生成されるファイルのテスト
DBIx::Schema::DSL、Const::Common::Generator、SQL::Translator::Producer::Teng、もしくは何らかのビルドツールを使ってソースコードを自動生成することがあるかと思います。それらの成果物をcommitするか、.gitignoreするかは状況に応じますが、commitする場合、生成元と生成先に齟齬があったら大変です。以下の様なテストが必要でしょう。
- 正しく自動生成できるか
- 自動生成されるはずのものと、実際にコミットされているものに齟齬がないか
- 自動生成されたものにシンタックスエラーなどはないか
バッチ、crontabのテスト
ある程度の規模になるとサービス運用においてcronは欠かせません。しかし本番で動かしてみるまではなかなか把握できないことも多いです。動いているつもりが動いていなかったなども経験したことがあることでしょう。本番反映前にできるかぎりテストしておきたいところです。
crontabは内容をリポジトリにコミットし、それに対してテストを回し、その内容を本番に反映するようにすると良いでしょう。テストの内容は以下などです。
- crontabにシンタックスエラーがないか、意図せぬ危険な設定がされていないか
- 指定コマンドやファイル名をtypoしていないか、パーミッションは適切か
- 狙った時間に実行されるか/変な時間に暴発しないか
crontabのparseにはParse::Crontabが便利です。また実際のcronの実行にあたっては、cronlogやruncronなどのラッパーを統一的に噛ますと良いでしょう。
cronの話は、Web+DB Pressに書かせてもらったcron周りのベストプラクティスも併せて御覧ください。
シソーラスのテストやスペルチェック
サービスのお知らせやヘルプなどで文章を書くこともあるでしょう。その内容も余力があればテストできるとベターです。
単純なスペルチェックにとどまらず、例えば長音表記や(ユーザーかユーザか等)、表記の統一(メトリックかメトリクスか等)などの表記揺れのチェックや、サービスの性質上好ましくない文言を使っていないか等のチェッカを走らせると良いでしょう。こういったものは指摘しきれるものではありませんし、ルールも追加されていくものなので、テストで検知できるに越したことはありません。クリティカルではありませんが、サービスのポリシーや世界観の統一のために必要なことです。
また、スマホアプリでは表現のためにフォントイメージを同梱することがありますが、そのフォントに入っていない文字をゲーム内で表示しようとしていないかどうかのテストも必要です。
マスタデータのテスト
本番環境に投入するデータに齟齬があっては大変です。例えばゲームだと以下の様な不具合が考えられます。
- ランクが低いアイテムなのにステータスが高すぎる
- アイテムの買値よりも売値のほうが高い
- セット販売なのに、バラで買ったほうが安い
- リレーション先の設定がない
これらは、ショッピングサイトの商品マスタなどでも起こりえる問題です。マスタデータはCSVやYAMLなどをリポジトリ管理し、テストを回してから本番投入した方が良いでしょう。マスタデータはエンジニア以外が作ることも多いと思うので、作成者にCIへの理解を求めることも大事です。
マスタデータのテストは素朴にマスタデータのファイルを舐めても良いかと思いますが、DBIx::FixtureLoaderやTest::mysqld::DataDirdumperなどを使って実際にテスト用のDBに投入してテストをしても良いでしょう。
ちなみに、PostgreSQLだと、チェック制約をかなり柔軟に使うことができるので、便利だなーと最近感じています。
リソースのテスト
マスタデータにかぎらず、例えば必要な画像が適切にコミットされているか等のテストはあったほうが良いでしょう。テストで検知できる項目はテストするに越したことはありません。
筆者は、ゲームの敵のAIに指定されている動作アニメーションが実際のアニメーションファイル内に存在しているかどうかのチェックをするためのテストを書いたことがあります。これは、SCMLという独自形式のXMLファイルの解析が必要で苦労を伴いましたが、これを検出できないと、敵がノーモーションで攻撃を繰り出してきたり、最悪フリーズしたりするのでとにかくテストが必要でした。
デプロイのテスト
デプロイには緊張感が走るのでテストできることはテストしておいたほうが良いでしょう。以下の様なものが挙げられます。
- 設定が正しいか
- スクリプト類が正しく動作するか
- ロールバックが正常に行えるか
なかなか厳密にやるのが難しい箇所もありますが、出来る範囲でテストすると良いでしょう。
インフラのテスト
これは近頃よく議論されているのでピンとくる人も多いでしょう。逆に筆者はそこまで専門ではないので深入りはしません。
プロビジョニングした環境が適切にセットアップされるかServerspec等を使って確認し、さらにはInfratester等を使って振る舞いのテストも出来ると良いでしょう。Dockerなどと組み合わせて高速にCIを回せると尚良いかもしれません。併せてミドルウェアの設定ファイルのテストも書いておくとよいでしょう。
以上、筆者がこれまで書いてきたソースコード以外に対するテストケースを挙げてみました。
これらはあくまで例であって、全てのプロジェクトに適用しなければならないわけではありません。またプロジェクトによっては他にテストが必要な項目もあることでしょう。
大事なことは以下のようなことです。
- プロジェクトにとって本質的なところをテストする
- 危ない所や心配なところ、事故りそうなところをテストする
- やりたくない作業を安心してやるためにテストを充実させる
そして、CIがソースコードではなく「プロジェクト」に対する継続的な監視である以上、CIの重要性を非エンジニアにも理解してもらうようにしましょう。「こういう箇所が心配だからこういうテストを追加してくれないか」とディレクターやデザイナーが言ってくるようになればしめたものです。
よく言われることですが、コードカバレッジは指標の一つに過ぎず、目的ではありません。コードカバレッジを99%から100%に上げることに躍起になるよりも、他にもっと本質的で必要なテスト、簡単に自動化できて事故を防げるテストが無いか、もう少し広い視野でプロジェクトを見なおしてみても良いのではないでしょうか。もちろんコードカバレッジが低くても良いという話ではありません。
本エントリでもいくつかCPANモジュールを取り上げましたが、Perlにはプロジェクトの本質的なところをテストするための細かい仕組み、かゆいところに手が届く優れたライブラリが整っています。僕が上記のような部分に対してテストを書こうと思ったのもPerlのテスト文化に育てられたからでしょう。またこれらは別にPerlに限らない汎用的な考え方でもあります。
このエントリはPerl Advent Calender 2014の最終日の記事でした。
Perl Advent Calenderは例年通り誰が始めるかチキンレースの様相を呈していましたが、チキンレースに負けて11/28に立ち上げてしまいました。当初は記事が集まるか非常に不安でしたが、いざとなったら空いたところは自分で全部埋めようと思っていました。しかしそれも杞憂に終わり、ちゃんと25日埋まって安堵しています。今年は、フレッシュな参加者も多かったのも良かったですね。
参加者の皆様、お疲れ様でした。良いお年を。