【改善版】.mock_in_sequence を作ってみました。
前回の投稿。
前回のresults.verify(self)のselfの不気味さをなんとかせねばとポンコツ君は奮闘しましたとさ。
気負い過ぎてしまった感がアリアリだったので、少し基本に立ち返ることにしました。次の仕様に変更することにします。
mock_in_sequence(object, target_method, exp_sequence_args)
レシーバを引数に収めて、関数的に仕上げた方が、よっぽどマシであるとの思いに至りました。mock_in_sequenceは、MockVerifyのファクトリーなんだと云う見方で納得することもできますけどね。
改善後のモジュールは、次の通り。
# Purpose: The methods in a target method are mocked in sequence
# mock_in_sequence(object, target_method, exp_sequence_args)
# object: testing object; instance object
# target_method: target method; symbol
# exp_sequence_args: exp_sequence_args is an array of methods
# and arguments that you want to mock in order
# exp_sequence_args = [
# [:method, retval, [args]],
# [:method, retval, [args]],
# ...
# ]
# eg. class MyClass
# def show
# show_header
# show_detail(arg1, arg2)
# show_footer
# end
# end
#
# class MyClassTest < MiniTest::Test
# include MockInSequence
#
# def setup
# @my_class = MyClass.new
# end
#
# def test_show
# exp_sequence_args = [
# [:show_header, true],
# [:show_detail, true, [arg1, arg2]],
# [:show_footer, true]
# ]
# results = mock_in_sequence(@my_class, :show, exp_sequence_args)
# results.verify
# end
# end
module MockInSequence
def mock_in_sequence(object, target_method, exp_sequence_args)
stub_stream = "object.#{target_method};"
act_sequence = Array.new
mocks = Hash.new
exp_sequence = exp_sequence_args.map(&:first)
exp_sequence_args.reverse!
exp_sequence_args.each do |(name, retval, args)|
mocks[name] = MiniTest::Mock.new
if args.nil?
mocks[name].expect(:call, retval) { act_sequence << name }
else
mocks[name].expect(:call, retval) { act_sequence << name; args }
end
stub_stream = "object.stub(#{name.inspect}, mocks[#{name.inspect}]) do; #{stub_stream} end;"
end
eval(stub_stream)
MockVerify.new({ mocks: mocks, expected: exp_sequence, actual: act_sequence }, self)
end
class MockVerify
def initialize(results, test_obj)
@test_obj = test_obj
@mocks = results[:mocks]
@expected = results[:expected]
@actual = results[:actual]
end
def verify
@mocks.values.map(&:verify)
@test_obj.assert_equal @expected,
@actual,
'note: not expected the order of sequence.'
end
end
end
今回、特に勉強になる着眼点はありません。mock_in_sequenceのレシーバの引数に移動したことによる変更だけです。これによって、results.verify(self)の不気味さが無くなり、results.verifyとするだけで良くなりました。
敢えて着眼点を申し上げるとするならば、コメントを大量に書き込んだことです。引数の数が多いのと使い方が特殊なので、忘れてしまった時のため、積極的に入れてみました。
サンプルのクラスは、前回と同じ。こちら。
class MyClass
def show
show_header
show_details
show_footer
end
def show_header; end
def show_details(condition = true); end
def show_footer; end
end
class MySubClass < MyClass
def show
show_header
show_footer
condition = false
show_details(condition) # このメソッドが最後に位置し、引数も伴って変更されています。
end
end
テストプログラムは、次のようになります。
require 'minitest/autorun'
class MyClassTest < Minitest::Test
include MockInSequence # ここにモジュールをインクルード
def setup
@my_class = MyClass.new
end
def test_show
# @my_class.extend MockInSequence # 不要になったので、コメントアウト
exp_sequence_args = [
[:show_header, true],
[:show_details, true],
[:show_footer, true]
]
results = mock_in_sequence(@my_class, :show, exp_sequence_args)
# レシーバが無くなり、引数に移動
results.verify # 魔法の呪文が無くなりました。
end
end
class MySubClassTest < Minitest::Test
include MockInSequence # ここにモジュールをインクルード
def setup
@my_sub_class = MySubClass.new
end
def test_show
# @my_class.extend MockInSequence # 不要になったので、コメントアウト
condition = false
exp_sequence_args = [
[:show_header, true],
[:show_footer, true],
[:show_details, true, [condition]]
]
results = mock_in_sequence(@my_sub_class, :show, exp_sequence_args)
# レシーバが無くなり、引数に移動
results.verify # 魔法の呪文が無くなりました。
end
end
モジュールは、インスタンスにエクステンドしていたのが、テストのクラスにインクルードする形に変わりました。
results = mock_in_sequence(@my_class, :show, exp_sequence_args) results.verify
気持ち悪さが無くなり、断然マシになりました。実際に使ってもらえると努力が報われると云うもの。是非是非、お試しあれ。
追記 (2022/6/4):お詫びと報告
あぁ。。。引数が付いた場合の呼び出しで、引数の評価をきちんとしていないです。重大な欠陥ですね。やっぱり、ポンコツでした。なんとかせねば。
追記 (2022/6/4):修正を施した【陳謝版】は、こちら。
この記事が気に入ったらサポートをしてみませんか?