RubyのMochaはメタボ気味だった件について
id:suerさんとRubyのスタブ/モックフレームワークのMochaを追いかけてみたら、わりとメタボ(メタプログラミング)気味だった件について。
まとめ
Mochaはテストフレームワークのメソッドを書き換えて、魔術的なことを実現してるよ!
挙動
Mochaを使うとこんなことができるよ!
def test_f o = SomeClass.new # bar(4)が呼ばれることを期待するモック o.expects(:bar).with(4) o.foo(2) # o#bar(4)が呼ばれてないと、ここでエラーが発生する(!) end
なんだかメタボっぽいので、おっかけてみました。
expectsの登録
expectsが呼ばれると、その情報がexpectationsに登録されます。
# lib/mocha/mock.rb def expects(method_name_or_hash, backtrace = nil) iterator = ArgumentIterator.new(method_name_or_hash) iterator.each { |*args| method_name = args.shift ensure_method_not_already_defined(method_name) expectation = Expectation.new(self, method_name, backtrace) expectation.returns(args.shift) if args.length > 0 @expectations.add(expectation) } end
呼ばれたかどうかのチェック
Mockery#verifyが呼ばれると、全部のexpectationsが呼ばれたかどうかをチェックします。
# lib/mocha/mockery.rb def verify(assertion_counter = nil) unless mocks.all? { |mock| mock.__verified__?(assertion_counter) } message = "not all expectations were satisfied\n#{mocha_inspect}" if unsatisfied_expectations.empty? backtrace = caller else backtrace = unsatisfied_expectations[0].backtrace end raise ExpectationError.new(message, backtrace) end expectations.each do |e| unless Mocha::Configuration.allow?(:stubbing_method_unnecessarily) unless e.used? on_stubbing_method_unnecessarily(e) end end end end
テスト終了後にverifyの呼び出し
ここからがいよいよメタボ(メタプログラミング)っぽいところです。
まずmonkey_patchメソッドで、フレームワークにあったパッチをrequireしてます。
# lib/mocha/integration.rb def monkey_patches patches = [] if test_unit_testcase_defined? && !test_unit_testcase_inherits_from_miniunit_testcase? patches << 'mocha/integration/test_unit' end if mini_unit_testcase_defined? patches << 'mocha/integration/mini_test' end patches end # (略) Mocha::Integration.monkey_patches.each do |patch| require patch end
そして、requireするパッチは、各バージョンごと(!)に用意されています。
$ ls integration/test_unit assertion_counter.rb gem_version_203_to_209.rb gem_version_200.rb ruby_version_185_and_below.rb gem_version_201_to_202.rb ruby_version_186_and_above.rb $ ls integration/mini_test assertion_counter.rb version_140.rb exception_translation.rb version_141.rb version_13.rb version_142_and_above.rb
このパッチは、内部関数を書き換えることで、テスト終了後にverifyを呼び出すようにするものです。
# lib/mocha/integration/test_unit/gem_version_200.rb def run(result) assertion_counter = AssertionCounter.new(result) begin @_result = result yield(Test::Unit::TestCase::STARTED, name) begin begin run_setup __send__(@method_name) mocha_verify(assertion_counter) rescue Mocha::ExpectationError => e ... end end end end
結論
Mochaはメタボ気味。
追記
一緒にコードを読んでたsuerさんが、もっと詳細な記事(id:suer:20101005:1286293319)を書いてます。