2008年03月27日 03:00 [Edit]
「同じコード」の同じって何さ - TAPのススメ
問題は、この「同じコード」の定義。
「誰が書いても同じコード」は大事なことなのか - ひがやすを blogでも、「誰が書いても同じコード」にするってのは、そもそも無理だと思うんだよね。そうやって、わざわざドキュメントをたくさん書かせても、めためたなコードを書くやつはいて、総合テストするときに、現場は燃え上がるもの。ある程度の規模以上のプロジェクトなら、どこでもそんな感じじゃないかと思います。
同じ「書き方」をしなければならないのか?
結果が「同じ」ならいいのか?
もし後者だとしたら、実は
重要なのは、「誰でもメンテナンスできるコード」にすること。そのために、コーディング規約は、きちんと決めてみんなで守る、それ以上は、がちがちに縛る必要はない。
すら必要ありません。なぜなら、最悪コードは書き直せばいいんですから。
しかし、「そのコードが何をすべきか」がわかっていないと、たとえソースがあっても無力な場合がほとんど。奇麗なソースですらそうで、ましてやスパゲッティコードともなれば。
その意味で、実はコーディング規約より、メンテナブルなコードよりも役に立つのが、テスト。要はテストをパスしてしまえばどうコードしても構わない、というのがTDD = Test Driven Development =テスト駆動開発の考え方のベースとなっています。
のですが、TDD、Perlを除いていまいち普及してません。それがなぜかといえば、テストがあまりにも面倒--だと思われているからです。これを見れば、面倒だと思われるのも当然だと思います。
「TDDer養成ギブス」始めました - t-wadaの日記
public void testDefaultStackSizeShouldBeZero throws Exception {
assertEquals("作成直後のスタックのサイズは0であること", 0, stack.size());
}
Test::Unit - Rubyリファレンスマニュアル
require 'test/unit'
require 'foo'
class TC_Foo < Test::Unit::TestCase
def setup
@obj = Foo.new
end
# def teardown
# end
def test_foo
assert_equal("foo", @obj.foo)
end
def test_bar
assert_equal("bar", @obj.bar)
end
end
たかだかテスト一個のために、関数作れだのメソッド作れだのって上司に言われたら、私だってうんこ入りのタッパーを上司に送りつけたくなりますよ。私は実際にそうする度胸まではないので、assert_unko()を実装してmailで送りつける程度にとめとくとも思いますが。
そんなあなたにお勧めなのが、Test Anything Protocol、略してTAPです。略称のごとく軽やかです。元々Perlで使われていましたが、あまりにシンプルなので言語を問いません。なにしろテストプログラムは、標準出力にこんな風に出力するだけでいいのですから。
Test Anything Protocol - Wikipedia, the free encyclopedia1..N ok 1 Description # Directive # Diagnostic .... ok 47 Description ok 48 Description more tests....
実際にrubyでFizzBuzzをテストしてみましょう。
% ruby fizzbuzz.t 1..17 ok # 1 ok # 2 ok # 3 ok # 4 ok # 5 ok # 6 ok # 7 ok # 8 ok # 9 ok # 10 ok # 11 ok # 12 ok # 13 ok # 14 ok # 15 # got: # expected: FizzBuzz not ok # 16 # got: # expected: 16
これでも要点はわかりますし、
% ruby fizzbuzz.t | grep ^not not ok # 0 not ok # 16
で問題を発見できるという点もぐーですが、perlがインストールされていれば、華麗にテストを実行してくれるプログラム、proveも漏れなくついてきます。これ、Perl以外のプログラムでも、TAPに準拠していればテストしてくれるのですよ。
% prove fizzbuzz.t
fizzbuzz....FAILED tests 16-17
Failed 2/17 tests, 88.24% okay
Failed Test Stat Wstat Total Fail List of Failed
-------------------------------------------------------------------------------
fizzbuzz.t 17 2 16-17
Failed 1/1 test scripts. 2/17 subtests failed.
Files=1, Tests=17, 0 wallclock secs ( 0.00 cusr + 0.01 csys = 0.01 CPU)
Failed 1/1 test programs. 2/17 subtests failed.
では、実際のテストプログラムfizzbuzz.tと、テストプログラムを駆動するためのプログラムtap.rbを見てみましょう。
fizzbuzz.t
#!/usr/local/bin/ruby require 'tap.rb' require 'fizzbuzz.rb' test 17 is fizzbuzz(1), 1, 1 is fizzbuzz(2), 2, 2 is fizzbuzz(3), 'Fizz', 3 is fizzbuzz(4), 4, 4 is fizzbuzz(5), 'Buzz', 5 is fizzbuzz(6), 'Fizz', 6 is fizzbuzz(7), 7, 7 is fizzbuzz(8), 8, 8 is fizzbuzz(9), 'Fizz', 9 is fizzbuzz(10), 'Buzz', 10 is fizzbuzz(11), 11, 11 is fizzbuzz(12), 'Fizz', 12 is fizzbuzz(13), 13, 13 is fizzbuzz(14), 14, 14 is fizzbuzz(15), 'FizzBuzz', 15 is fizzbuzz(0), 'FizzBuzz', 0 is fizzbuzz(16), 16, 16tap.rb
def test(ntests=0)
puts "1..#{ntests}" if ntests > 0
end
def is(got, expected, message=nil)
print 'not ' if expected != got
print 'ok'
print " # #{message}" if message
print "\n";
if expected != got
puts "# got: #{got}"
puts "# expected: #{expected}"
end
end
tap.rb自体もFizzBuzz並みに簡単な点にご留意ください。
で、(わざとらしく)テストにこけたFizzBuzzが実装されているfizzbuzz.rbを見てみましょう。
def fizzbuzz(n)
return 1 if n == 1
return 2 if n == 2
return 'Fizz' if n == 3
return 4 if n == 4
return 'Buzz' if n == 5
return 'Fizz' if n == 6
return 7 if n == 7
return 8 if n == 8
return 'Fizz' if n == 9
return 'Buzz' if n == 10
return 11 if n == 11
return 'Fizz' if n == 12
return 13 if n == 13
return 14 if n == 14
return 'FizzBuzz' if n == 15
end
こんなの部下が書いたら今度は部下にうんこ入(ry
というのはおいといて、これを
def fizzbuzz(n)
fz = if n % 3 == 0 then 'Fizz' else '' end
fz += if n % 5 == 0 then 'Buzz' else '' end
if fz == '' then n else fz end
end
とでも差し替えて見ましょう。今度はどうでしょうか?
% prove fizzbuzz.t fizzbuzz....ok All tests successful. Files=1, Tests=17, 0 wallclock secs ( 0.00 cusr + 0.01 csys = 0.01 CPU)
このTAP、Wikipediaの説明でも充分なのですが、"Perl Testing"にはさらに詳しく載っています。まだ日本語版はなく、また頭に"Perl"とついてはいるのですが、内容は平易で、お値段もこの手の本としては3,000円を切っているので、PerlプログラマーでなくてもTDDをやろうとする人は目を通しておくべき一冊です。
assertにうんざりしているあなた、TestはもっとSimpleになりうるしそうすべきなのです。
Dan the TAP Dancer
この記事へのトラックバックURL
手で更新しなければ行けないんですか?
さすがスーパー・ハッカーは言うことが違う。100万ステップ超のぐちょぐちょコードでもさらっと解読して全部書きなおしも平気なんだあ。
弾さんのそのスーパーな能力が本当にうらやましいです。
「最悪コードは書き直せばいいんですから」っていうのは解読と修正にかかる時間が、一から書き直すよりも大きい(と判断した)場合でしょう。
alias is assert_equal と書けばもう大して変わらないんじゃね?
しかも、この記事の前段の例は一個だけでオブジェクトの生成を伴うもの、後段の例は十数個並べた上にstaticな関数のテスト。そんな比較のしかたはないよ。
TAPについては知りませんでしたが、ソースを開いたら、うんこ(修正前のfizzbuzz.rb など)がはいっていた際によくやる手を紹介。
これなら、解読に時間をかけなくても、良いです。
結果が同じになればいいという考え方です。
メソッド単位くらいで、組みなおす。(仮にfizzbuzz_nというメソッドを作成。)
fizzbuzz.t を(is fizzbuzz(1),fizzbuzz_n(1), 1)みたいにしてテスト。
テストがALL OKになれば、入れ替え。
(ただしケース漏れを防ぐのに、場合により、元のメソッドを分割したりすること。)
数百人が毎日うんこをどんどん生産してリビジョンがトイレに行く
回数くらい上がっていくという。悪夢のような。
まぁ、大規模開発を経験してなお「このテスト観」なら大したものです;)
