Rubyで一時関数を定義するにはどうすればよいですか?
概要
テストやその他の場合に繰り返しコードを記述することは避けたいと考えています。
不便なドット呼び出し構文test_parse_tag.(…)でラムダを使用する以外に良い方法はありますか?
def test(name) = yield
def assert_equal(expected, actual) = p :tested
class El
def self.parse_tag(s) = [:div, { class: 'some' }]
end
# I don't want to type `assert_equal El.parse_tag` every time
test 'El.parse_tag' do
assert_equal El.parse_tag('span'), [:span, {}]
assert_equal El.parse_tag('#id'), [:div, { id: 'id' }]
assert_equal El.parse_tag('div a=b'), [:div, { a: 'b' }]
end
# So I use shortcut `test_parse_tag`
test 'El.parse_tag' do
def test_parse_tag(actual, expected)
assert_equal El.parse_tag(actual), expected
end
test_parse_tag 'span', [:span, {}]
test_parse_tag '#id', [:div, { id: 'id' }]
test_parse_tag 'div a=b', [:div, { a: 'b' }]
end
# PROBLEM, I don't want `test_parse_tag` to be available here
test_parse_tag 'span', [:span, {}]
解決策
事前に作成したオブジェクトのコンテキストで、テストに渡されたブロックを実行できます。以下は、必要に応じてオブジェクトをブロック引数として渡す、instance_exec を使用した単純なバージョンです。
def test(name, &block)
obj = Object.new
obj.instance_exec(obj, &block)
end
オブジェクト内でブロックを実行すると、メソッドはオブジェクトのシングルトン クラスで定義され、外部に漏れることはありません。
test 'one' do |context|
def foo ; end
defined? foo #=> "method"
context #=> <Object:0x00007fd3b002c2f0>
method(:foo).owner #=> #<Class:#<Object:0x00007fd3b002c2f0>>
end
test 'two' do |context|
defined? foo #=> nil
context #=> #<Object:0x00007fd3b0026fd0>
end
defined? foo #=> nil
RSpec はこのアプローチをさらに一歩進めています。各サンプル グループがクラスを作成し、各サンプルがそのクラスのインスタンス内で評価されます。スコープについての説明を参照してください。