one assertion per testをツールで - dustを拡張してみる
ひとつのテストメソッドにはひとつのassert文、そしてEmacsサポート - http://rubikitch.com/に移転しましたにてone assertion per test推奨と言ったが、冗長すぎるのが少し嫌。
dustを使うと、
require 'rubygems' require 'test/unit' require 'dust' unit_tests do test "Foo#initialize test (a)" do x = Foo.new(:a=>1, :b=>2) assert_equal 1, x.a end test "Foo#initialize test (b)" do x = Foo.new(:a=>1, :b=>2) assert_equal 2, x.b end end
なんて書ける。内部で、
module UnitTest class Test1 < Test::Unit::TestCase def test_Foo_initialize_test__a_ x = Foo.new(:a=>1, :b=>2) assert_equal 1, x.a end def test_Foo_initialize_test__b_ x = Foo.new(:a=>1, :b=>2) assert_equal 2, x.b end end end
に変換される。一方、こんな記法はどうだろう?
unit_tests do test "Foo#initialize test" do x = Foo.new(:a=>1, :b=>2) check("(a)") { assert_equal 1, x.a } check("(b)") { assert_equal 2, x.b } end end
これも上のTest::Unitに変換されるようにする。Lispのマクロなら構文木いぢれるからこんなコードを生成できるけど、RubyだとParseTreeとか使わないとだめかも…それに1.9だと動かないかもしれんし。
one assertion per testするためにいちいちコピペしないといけないのは、実装上の都合があるようだ。
だけどこれならば、実現できるかもしれない。煩雑すぎるかな?
unit_tests do multi_test "Foo#initialize test" do before { Foo.new(:a=>1, :b=>2) } check("(a)") {|x| assert_equal 1, x.a } check("(b)") {|x| assert_equal 2, x.b } after { 後片付 } end end
実装
まぁとりあえずできた。提案として作者にメールしてみるか。
require 'rubygems' require 'test/unit' require 'active_support' # for instance_exec require 'dust' class Foo def initialize(hash) @a = hash[:a] @b = hash[:b] end attr_reader :a, :b end class Test::Unit::TestCase class DustMultipleTest def before(&block) @before_proc = block end def after(&block) @after_proc = block end def check(name,&block) (@checks||=[]) << [name, block] end attr_reader :before_proc, :after_proc, :checks end def self.multi_test(basename, &block) dmt = DustMultipleTest.new dmt.instance_eval(&block) module_eval do dmt.checks.each do |name, assert_block| test("#{basename} #{name}") do begin x = instance_eval(&dmt.before_proc) instance_exec(x, &assert_block) ensure dmt.after_proc and instance_exec(x, &dmt.after_proc) end end end end end end unit_tests do multi_test "Foo#initialize test" do before { Foo.new(:a=>1, :b=>2) } check("(a)") {|x| assert_equal 1, x.a } check("(b)") {|x| assert_equal 2, x.b } after {|x| } end end