概要
テストコードの中で何らかのデータを表で表現したいことはよくあること。
遠慮せずにCSV形式で埋め込んでしまいましょう!
例えばこんな感じで。コード例はGroovyです。
def setup() {
def csv = TestHelper.splitCsv("""¥
#0 1 2
#受注番号 顧客番号 希望納期
12345, C0001, 2010-10-10
23456, C0002, 2011-11-11
34567, C0003, 2012-12-12
""")
csv.each{row ->
def dto = new orderDto(
orderId: Integer.parseInt(row[0]),
customerId: row[1],
deliveryDate: LocalDate.parse(row(2)),
)
OrderTable.inset(dto)
}
}
TestHelper.splitCsv()はこんな感じの文字列のリストのリストを返すようなヘルパです。
こんなの鼻歌交じりで1分で作れますよね。
class TestHelper {
static List<List<String>> splitCsv(csv) {
def lines = csv.split("\n")
// #で始まる行は取り除く
lines = lines.findAll{!(it =~ /^#/)}
// 改行と前後の空白を取り除いたうえで、コンマで区切る
lines.collect{
it.replaceAll(/(\r|\n)/,"").trim().split(/\s*,\s*/)
}
}
}
なぜコード例がGroovyなのか。それはJavaにはヒアドキュメントがないからです。
CSVファイルをIMPORTしてはダメなのか
あれは私がユニットテストを知識としてしか知らなかった頃。
はじめてリードプログラマとなったプロジェクトでした。ちゃんとテストを書こうねとメンバーと話し合いました。言語はVB.netだったので、NUnitを使いました。
さて、データベースクエリをラップしたメソッドのテストを作りましょう。要するにSQLをテストするのですから、setupでテーブルに初期データをセットしたいです。あるメンバーが、CSVファイルにした初期データを簡単にテーブルにIMPORTするテストヘルパを作ってくれました。嬉しかった。ありがとう!
しかし、開発を進めるうちにこのやり方は 良くない ことがはっきりしました。
データが勝手に変えられてしまう
あなたが新しくSQLを書いて、それをテストすることになったとしましょう。そして、あるテーブルにデータをセットしなければならない。ふと、テストがあるディレクトリを見ると、そのテーブルのIMPORTデータのCSVファイルがありました。
そのまま使いますよね?
そのデータを見ると、自分のテストに欲しいデータが微妙に足りないことがわかりました。
足しますよね?
すると、他のテストが失敗するようになったりします。ありがちです。
しかし、実は他のテストが失敗しないケースの方が問題でした。
テストデータというのは、テストの意図が反映されるものです。データの1件1件が、仕様1つ1つに紐付いていたりします。2レコード目はAという条件に引っかかるから除外、3レコード目はBという条件に・・・という具合です。
このようなテストデータに1件付け加えられてしまうと後からこのテストを理解しようとする人が、「あれ?このデータの存在している理由はなんだ?」と思ってしまうのです。
テストケースとテストデータは本来、密接に紐づいているものなのです。
表現力が足りない
テストデータもテストの意図を伝える大事な要素であることがわかってくると、IMPORT用のCSVデータでは表現力不足を感じるようになりました。
例えば、テストのためにはこのテーブルの2列だけが重要で他の列はどうでもいいというようなことがよくありますが、どうでもいい列を省いたり、大事な列をまとめて左側に記述したりということができません。
また、受注と受注明細のように本来強く紐づいたデータを複数のテーブルで表現する場合、複数のファイルに分かれていると理解するのに手間取ります。テストデータを見やすくするために「CSVファイルの最初の5列を受注テーブル、以降の列を受注明細に入れたい」などという欲求が生まれ、実際に1ファイルから2テーブルへIMPORTするツールを書く人も出てきました。
テストケースにデータを埋め込むべし
そもそも、テストデータはケースを表現する一部なので、テストケースから引き離されるべきではなかったのです。
しかし、オブジェクトの初期化構文やJSONのような形でデータを記述してみると、CSV形式に比べて別の不満が出てきます。
- 一覧性に乏しい
- プロパティ名などが何度も出てきて冗長
- 文字列データがたくさん出てくるのに、いちいち
""
で囲むのが面倒くさい
ならば、CSVの形式で埋め込んでしまえばいいじゃないですか。コード中にデータを入れるならば、ヒアドキュメントでしょう。
当時のVB.netにはそのものズバリのヒアドキュメントは存在しなかったのですが、
複数行に渡る文字列リテラルが欲しければ、XMLリテラルを使う荒技がありました。
こんな感じです
Dim csv = TestHelper.splitCsv(
<Data>
#0 1 2
#受注番号 顧客番号 希望納期
12345, C0001, 2010-10-10
23456, C0002, 2011-11-11
34567, C0003, 2012-12-12
</Data>)
突然のタグの登場にちょっとびっくりしますが、Valueプロパティで文字列としてタグで囲んだ部分を取得できます。
コーディングルールとして、使うタグは<Data>
に固定しました。メンバーから「なぜ?」と聞かれたので、 「BASICにデータを埋め込むんだからDATAに決まってんだろうが」 と力強く答えました。ぽかんとされました。
Javaで挫折し、Groovyに救われた
時は流れ、新しくJavaのプロジェクトをやることになりました。同じようにテストデータをケース中に埋め込みたいと考えましたが、Javaにはヒアドキュメントがないので挫折しました。
いろいろと悩んだ結果、Javaでテストを記述するのを諦めてGroovyを使うことにしました。それが冒頭の例です。
詳細は、すでに素晴らしい資料があるのでそちらに譲ります。
JavaのテストGroovyでいいのではないかという話
GroovyでJavaのテストを記述することのメリットは非常に大きいのですが、特に
- ヒアドキュメント(GString)がある
- 同値比較がequals()ではなく==
- PowerAssert内蔵
- JavaのPrivateを無視できる(というか、Javaの可視性をサポートしてない)
の4点はとてもテストに向いた性質なので、JavaのテストをGroovyで書くのはお勧めです。