るびきちです。

1つのファイルですむレベルの短いRubyスクリプトをTDDするとき、
みなさんどうしていますか?

10分で書き上げるスクリプトのためにわざわざ別のファイルにわけてテストを
かくのは面倒ですね。かといって if __FILE__ == $0 中にテストを書こうにも、
実装メソッドとテストメソッドの距離がはなれてしまうので嫌です。それに
ちょっとしたスクリプトのテストに「おまじない」を書くのも嫌です。なので
rbtestというスクリプトを作成してみました。

rbtest script-file-name
で埋め込まれたテストを実行できます。

rbtestはRubyスクリプトを読み、
  =begin test_foo
などのbeginブロックを発見したとき、
test_fooをテストメソッドにした一時テストスクリプトを作成、実行します。
実行後必ず一時テストスクリプトは消去されます。

さらにemacsのnext-errorで捕獲できるよう工夫してあります。
* 一時テストスクリプトのファイル名を実行スクリプトのファイル名に書き換える。
* 行番号を揃えておく。

#### 実行スクリプト
#!/usr/bin/env ruby
def f(x)
  x+1
end
=begin test_f
# wrong test
assert_equal 120, f(11)
=end

=begin
This block is ignored!
=end

def g(x)
  x**2
end

=begin test_g
assert_equal 4, g(2)
assert_equal 9, g(3)
=end

if __FILE__==$0
  puts f(3)
end

#### rbtestにより生成された一時テストスクリプト
#!/usr/bin/env ruby



require 'test/unit';load 'sample1.rb';class TestByRbtest < Test::Unit::TestCase;def test_f
# wrong test
assert_equal 120, f(11)
end









def test_g
assert_equal 4, g(2)
assert_equal 9, g(3)
end




end

#### 実行結果
Loaded suite ./sample1.rb__test__
Started
F.
Finished in 0.012425 seconds.

  1) Failure:
test_f(TestByRbtest) [./sample1.rb:7]:
<120> expected but was
<12>.

2 tests, 3 assertions, 1 failures, 0 errors



#!/usr/bin/env ruby # -*- ruby -*- # Usage: # rbtest script [Test::Unit options] # rbtest script output # output temporary test script # preamble def first_test(script_filename) "require 'test/unit';" + "load '#{script_filename}';" + "class TestByRbtest < Test::Unit::TestCase;" end def script_to_test_script(ruby_source, script_filename) _SKIP = "\n" state = :start ruby_source.map do |line| case line when /^#!/ line when /^=begin (test\S*)$/ # test method block preamble = (state == :start) ? first_test(script_filename) : "" state = :test_method preamble + "def #{$1}\n" when /^=begin/ # other block state = :begin _SKIP when /^=end$/ if state == :test_method state = :script "end\n" # /def test_xxx else _SKIP end else if state == :test_method line else _SKIP end end end.join + "end\n" # /class TestByRbtest end def execute_test_script(test_script_filename, argv) output = `#{test_script_filename} #{argv[1..-1].join ' '} ` print output.gsub(Regexp.new(Regexp.quote(test_script_filename)), argv.script_filename) end def generate_test_script(script_filename, test_script_filename) open(test_script_filename, "w") do |f| f.write(script_to_test_script(File.read(script_filename), script_filename)) f.chmod 0755 # executable end end ################ def do_test(argv) begin test_script_filename = "#{argv.script_filename}__test__.rb" generate_test_script(argv.script_filename, test_script_filename) execute_test_script(test_script_filename, argv) ensure File.unlink test_script_filename end end def output_test_script(argv) print(script_to_test_script(File.read(argv.script_filename), argv.script_filename)) end class << ARGV alias :script_filename :first end if __FILE__ ==$0 if ARGV[1] == "output" action = :output_test_script else action = :do_test end __send__(action, ARGV) end
-- rubikitch http://www.rubyist.net/~rubikitch/