ふつうのユニットテストのための7つのルール

最近、久しぶりにコードレビューをすることが増えたのですが、UnitTestのコードを見るとヒドイ部分が多く残念な気持ちになることもあります。

原因のひとつとして、プロダクトコードと違いテストの書き方をあまり書き方を明文化してなかったのが悪かったなと思い、とりあえず明文化してみました。

今回は、命名規則とかそのレベルまではいかず「ユニットテストかくあるべし」ってところまでをまとめます。正直、これ守ってくれたらあとは好みの世界もあるしね。

追記: テクニカルな部分も最低限ですがQiitaに記載しました。

qiita.com

追記: もうちょっと大上段の規約に関してもまとめてみました。

koduki.hatenablog.com

前提

ここではユニットテストを関数レベルのテストをJUnitのような自動テストツールで取り扱う場合に限定します。 また、Mavenでビルド時は常にテストを回すことを想定しているので、それを踏まえたルールだと思ってください。

ルール

ポータビリティを大事にする

CIサーバや他人のPCで動かないテストコードを書いてはいけません。他所で動かないコードは不要では無く害悪です。 後述のルールでも具体的に触れていきますが、まずこの大前提を忘れないでください。

プロジェクト配下のディレクトリのみを使う

本番環境の「/var/hogedata」や「D:\hogedata」みたいなパスを前提にしたテストを書いてはいけません。 gitから落としたら即座に利用できるようにsrcまたはtarget配下を前提として相対パスでテストデータの配置やファイル入出力を行ってください。

DBやネットワークといった外部環境への依存を無くす

日付やDBやネットワーク(WebAPIとか)といったものに依存するコードをユニットテストに書くべきではありません。ユニットテストはあくまでロジックの検証にとどめるためにMockやその他テストダブルを活用してください。 ネットワークの場合はBetamax、それ以外の場合はJMockit等を使うこともできます。 DIコンテナやフィールドをパッケージレベルにするとで依存性を注入できるようにするとなお良いです。

SQLそのものをテストしたい場合等の重たいテストはIntegration Testとして、mavenのテストフェーズではなくintegration-testフェーズ等で実行してください。

繰り返し実行できるように作る

上記、2つのルールと被るところもありますが、手動手順無く繰り返し実行できるように作る必要があります。 環境の初期化と後始末を含めてすべてをテスト内で表現してください。setUpとtearDownで初期化とクリーンアップを適切に実施してください

標準出力にログを出さない

printlnや各種Logger等でユニットテストのために標準出力にログを出してはいけません。テストの結果は人間が目視で確認するのではなく、すべてコード上のアサーションに表れているべきです。 結果としてユニットテスト向けに人間が見るためのログが出ることはノイズにしかならないので、原則出力するべきではありません。 特に「エラーが正常に出ることを確認する場合」にprintStacktraceをするのはログが汚染されて本当に困るのでやめてください。本当のビルドエラーを追うのが困難になります。

とはいえ、ユニットテストのデバックのためにログを出したいケースもあると思いますので、その場合は一時的に書いてちゃんと消すか、traceレベルのログを使ってください。

ひとつのテストケースにはひとつの観点しか検証しない

一つのテストケースでは一つの観点でしか検証をしてはいけません。テストがこけたときに原因の切り分けが出来なくなるからです。 手動で実行してる分けではないので、テストケースの圧縮を過度にする必要はありません。

もちろん、同じ観点でのパラメータ化テストや、一つの検証観点で複数の検証項目がある(例えばBeanの各プロパティをチェックとか)場合にassertをいくつも書くのは問題ないです。

問題なのはテストXを実行したときに「ロジックAの動作」と「ロジックBの動作」を同時に検証することです。たとえ、AとBが依存してても個別のテストケースに落とすべきです。

ユニットテストを見て何をしてるか分かるように書く

稀にユニットテストの中が「test001」から「test010」まで並んでいて、なにを検証してるのかが別で作られてるExcelのテスト仕様書をみないとサッパリわからないUTがあります。 テストケースはふるまいを確認するための重要なコードなので、きちんと名称等にも気を使って書くべきです。

日本語圏のエンジニアが中心ならテストケース名を日本語で書くのもありでしょうし、せめてコメントで記載されてるだけでもずいぶん違います。最悪でもそのテスト仕様書の場所をコード内に書いてください。

BDDテストツールを使えばそれがベストかもしれませんが、そのあたりは本題から外れるので割愛。

番外ルール

「-Dmaven.test.skip=true」ダメ、ゼッタイ!

ちょっと粒度が違うので番外ルールですが、気軽に「-Dmaven.test.skip=true」を使うのはダメダメです。

これは「CI上ですでにいろんなテストがパスしたことを確認したのちにリリース担当者がビルドするとき」とか、結構特殊なケースのみで使うべきものです。

「あれ? なんかビルド失敗する。テストか、とりあえずskipしよう」みたいな事考えだすと後からひどい目にあいます。

CI使ってもテストがこけるのは分かるようになりますが、例えば標準出力の汚染とかビルド時間の増加とかは分かんないので、みんなが日々ビルドしてることが大事ですね。

テストとコンパイルとパッケージを含めてこそのビルドです。

まとめ

さて、いくつかまとめてみましたが、あくまで個人的なポリシーなので異論については大いに議論したいなと思います。

このあたりを整備した後はもっと具体的な命名規則とかテストの種別によるJUnitや各テストツールの具体的な使い方をまとめた資料が必要になってる来ると思いますが、テクニカルなところは結構世の中にも良い資料がたくさんある気がするのでそっちにお任せ。

またMavenを前提に考えてますが、今ならDockerがローカルにもCI環境にもあることを前提にすれば、また違った考え方は出来そうですね。

それではHappy Hacking!