テストファーストに意味はあるのか?
一人アドベントカレンダー 2016 1 日目 by @takkyuuplayer
1行まとめ
ビギナーエンジニアはテストを先に書いたほうがいい
概要
プログラミングの世界において、今実装しているものが仕様を満たし且つ今後も動き続けると自信を持って言うためにはどうすれば良いだろうか? 当然「テストを書く」という事になる。テストを書くという文脈でよく語られるものに「テストが先か、実装が先か?」がある。
どっちでもいいと思っていたが、最近ビギナーエンジニアに限って言えば「テストを先に書いたほうがいい」と思うようになった。 その理由を述べたい。
テストコードとは、動く仕様書/ドキュメント
関数 `add` は 2つの数を受取り、足し算してそれを返す
という関数があるとする。これにテストコードを書くと↓のような感じになると思う。
use Test::More; sub add { # どう実装しているかは知らない } subtest "add は2つ引数を受け取って、足し算して返す" => sub { is add(2, 3), 5; }; done_testing;
これはただ日本語の仕様書を、動くコードに 「翻訳」しただけだ。 テストコードを書くとは、「仕様書を動くコードに翻訳する」あるいは仕様が頭の中にしかないのであればそれを「動くドキュメント化する」という行為である。
先に実装を書くと何が起きるか?
仕様書の翻訳だったはずの「テストコードを書く」という行為が 「実装を読んで、こういうテストケースを書けば通る」というコードを発見する行為に成り下がる。
関数 `total_price_including_tax` は品物の値段を複数受けとり、消費税込の合計金額を返す
という関数を作りたいとする。その仕様に対し
sub total_price_includ { my $sum = 0; for my $price (@_) { $sum += $price; } return $sum * 1.08; }
という実装を行い、意気揚々と次のようなテストを書く。
use Test::More; subtest 'Test total_price' => sub { is total_price_including_tax(100, 200, 300), 648; }; done_testing;
そして、pull request を送って突っ込まれる。
「品物の値段を受け取る関数なのに、負の値段を受け取ってもいいんですか?」
「負の値段を渡したときのテストはしておかなくて、大丈夫ですか?」
そうなるのも仕方ない。それらしいテストケースを発見して、それを書いただけなのだから。
テストから先に書くとどうなるか?
use Test::More; subtest '品物の値段を複数受けとり、消費税込の合計金額を返す' => sub { is total_price_including_tax(100, 200, 300), 648; }; done_testing;
と書いたときに、頭の中には仕様があるので「品物の値段っていうことは 非負整数 のチェックをしておいたほうが良さそうだぞ」と気付きテストケースを追加する。
use Test::More; use Test::Exception; subtest '品物の値段を複数受けとり、消費税込の合計金額を返す' => sub { is total_price_including_tax(100, 200, 300), 648; dies_ok { total_price_including_tax(100, -200, 300) }; }; done_testing;
これで total_price_including_tax
はバッチリだ。
ビギナーとベテランの違い
total_price_including_tax
は極端な例だ。
引数となる値段はどこか別の場所でバリデーションされており非負整数のチェックをする事は冗長なだけかもしれないし、
total_price_including_tax
を実装している段階で「あ、負整数は弾かないと」と気付くことも可能だ。
しかしビギナーエンジニアに関して言えば、言語仕様であったりライブラリの使い方がよく分かっていなったりで 実装中に「〇〇ってどうやって書くんだっけ?」と頭が一杯一杯になることがある。 そしてその瞬間に言葉の中に隠れた仕様(値段だから、引数は0以上)は頭から吹っ飛んでしまう。
テストから先に書いておけば、実装中に頭が一杯一杯になっても安心だ。
まとめ
そんなわけで
ビギナーエンジニアはテストを先に書く
ことを推奨したい。
次回予告
明日の担当も @takkyuuplayer です。