WEB+DB PRESS休刊に伴い、今回からWeb上で連載を継続させていただくことになりました。今後とも何卒よろしくお願いします。さて、あらためて本連載の最近の連載のテーマを振り返りますと、それは
多くのテスティングフレームワークには実行結果の出力フォーマットを変更するオプションやプラグイン機構があり、自動テストはその実行結果を様々なフォーマットで出力します。それらテストの実行結果は
信号機としてのテスト出力
意思決定から行動へつなげる最初の判断材料は、実行した自動テストがすべて成功したかどうかです。自動テスト群がすべて成功した場合、次の行動として、プルリクエストやブランチのマージ、ステージング環境や本番環境へのデプロイなどに進みます。
このため、自動テストの出力にまず求められるのは、実行したテスト群がすべて成功したのか、それとも失敗したテストがあったのかを明確に伝えることです。実行結果が全件成功であったかどうかがすぐにわかると、実行結果全体を詳細に見なくとも、次の行動を判断しやすくなります。このような信号機としての役割には、情報の受け手が人間の場合は色が、コンピュータの場合は終了コードがよく使われます。
人間はテスト実行結果の色で判断します。多くのテスティングフレームワークでは失敗を赤、成功を緑でプログラマに結果を伝えます。実行結果のサマリー部分が緑色で表示されていれば成功、赤色で表示されていれば失敗と瞬時に判断できます。
コンピュータはコマンドの終了ステータスコードで判断します。多くのテスティングフレームワークは自動テストのコマンド成功時に0、失敗時には1以上を返します。結果の成否がコンピュータに判断しやすく、後続のコマンドにUNIXのパイプ等でつなぎやすくなります。
問題箇所の特定のためのテスト出力
失敗した自動テストがあるときの次の行動は、問題箇所の特定と修復です。このため、失敗時の出力に求められるのは、迅速に問題箇所の特定と原因究明を行い、修復に向かうための情報源となることです。知りたいのは
何が失敗したか
「何が失敗したか」
「何が失敗したか」
どこで失敗したか
「どこで失敗したか」
- Execution Error: テスト実行中にプロダクトコードから発生する実行時エラー
- Assertion Failure: テストコードの中に書いた表明
(アサーション) の失敗
Execution Errorは、テスト実行中にプロダクトコードから発生した実行時エラーです。プロダクトコードの変更直後などによく発生しがちで、単純なミスのことも多くあります。Execution Errorの患部はスタックトレースやログに現れます。
Execution Errorの問題箇所の絞り込みやすさは、テストサイズを小さくすることによって改善されます。テストサイズが小さくなるにつれてExecution Errorが記録されたスタックトレースやログにアクセスしやすくなるからです。特にテストサイズがSmallの場合は患部がテスト実行時のスタックトレースに直接現れるので、問題箇所の特定が非常に容易になります。これはテストサイズを小さくする利点です。テストサイズに関しては連載の第3回 テストサイズ ~自動テストとCIにフィットする明確なテスト分類基準~を、テストサイズを小さくする方法に関しては、連載の第6回 自動テストのサイズダウン戦略 ~テストダブルを作る前に考えるべきこと~をご覧ください。
Assertion Failureは、テストコードの中に書いた表明
Assertion Failureが発生した場合、患部はプロダクトコードのどこかにありますが、テストの実行エラーは発生していないので患部がどこにあるかはわかりません。このため修復に必要な時間もわかりませんが、問題箇所の絞り込み速度はテスト範囲
どのように失敗したか
「どのように失敗したか」
「どのように失敗したか」
悪い例ではAssertion Failureの際の情報量が足りません。trueを期待していたがfalseが返ってきた程度の情報量しかないのでテスト対象から実際にどのような値が返ってきたのかわからず、判断が難しくなってしまいます。情報量を増やすためには、より詳しいアサーションに書き直して再実行しなければなりません。
良い例では、テスト対象から返ってきた39という値が期待値である40と一致しないためテストが失敗していることがわかります。期待値と実測値の値の差が1なので、テスト名から判断するに、税額の切り捨てまわりのロジックを怪しいとにらんで確認するという行動に移れそうです。なお、アサーション関数の引数の順番に注意してください。期待値と実測値の順番は、残念ながら言語やフレームワークによって異なります。ここで例として挙げているJUnitにおいては、期待値を第1引数、実測値を第2引数に渡します。
アサーションの使い方を誤っても成功時の情報量はほぼ変わりませんが、失敗時の情報量は大きく変わります。いざテストが失敗したときに役に立たないテストになってしまわないように注意しましょう。
ドキュメントとしてのテスト出力
情報を取得する目的には、開発の進捗の把握や、テストケースの網羅性の評価といった観点もあります。例えば機能開発ブランチやGitHubのプルリクエストで開発しているときに、開発対象の機能はどのような仕様か、その機能がどこまでできあがっているか、テストは十分かなどを評価するために、テスト実行結果を使うこともできます。
テストケースの構造や名前に沿った出力を行うフォーマットを選択すると、開発中に自分で確認したり、開発がある程度進んでから第三者にレビューしてもらったりといった用途に使えます。テスト実行結果をドキュメントとして活用しやすくするポイントは、テストの構造や名前に気を配ることです。詳しくは連載の第7回 テストコードの認知負荷 ~テストの名前、構造、情報量を工夫する~を参照ください。
私がGitHub上で開発しているextract-git-treeishのテストをspec
フォーマット」
と呼ばれる形式を指定して出力した実行結果の一部を例に挙げます。テスト実行結果がそのまま仕様のツリーになっていることが読み取れるのではないでしょうか。これをさらにMarkdown形式で出力されるように工夫すると、そのままREADMEなどにドキュメントとして貼り付けられます。
> extract-git-treeish@3.1.0 test:spec > mocha test --reporter spec `exists({ treeIsh, [gitProjectRoot], [spawnOptions] })`: Inquires for existence of `treeIsh` returns `Promise` which will: ✔ resolve with `true` when tree-ish exists ✔ resolve with `false` when tree-ish does not exist `treeIsh`(string) is a name of a git tree-ish (commit, branch, or tag) to be inquired when `treeIsh` argument is omitted: ✔ throw TypeError when `treeIsh` argument is not a string: ✔ throw TypeError `gitProjectRoot`(string) is an optional directory path pointing to top level directory of git project when `gitProjectRoot` option is omitted: and when `process.cwd()` is inside the git project: ✔ resolves as usual and when `process.cwd()` is outside the git project: ✔ returns `Promise` which will reject with Error when specified `gitProjectRoot` is pointing to git project root: ✔ resolves as usual when specified `gitProjectRoot` is not a git repository (or any of the parent directories): ✔ returns `Promise` which will resolve with `false` when specified `gitProjectRoot` is pointing to directory that does not exist: ✔ returns `Promise` which will reject with Error when `gitProjectRoot` argument is not a string: ✔ throw TypeError when number ✔ throw TypeError when boolean
データとしてのテスト出力
ここまでは
コードカバレッジ
おわりに
意思決定と行動を促すための情報という観点で自動テストの出力を捉えましょう。