いくつかのクラスに混在するモジュールの単体テストの記述方法を知りたいのですが、どうすればよいかわかりません。
それらを含むクラスのテストファイルの1つにテストを記述してインスタンスメソッドをテストしますか(正しくないようです)、またはモジュールに固有の別のファイルに含まれているメソッドのテストを保持できますか?
同じ質問がクラスメソッドにも当てはまります。
通常のRailsモデルのように、モジュールのクラスごとに個別のテストファイルを用意する必要がありますか、それとも一般モジュールのテストファイルに存在しますか?
私見、モジュールのすべての使用法をカバーする機能テストカバレッジを実行し、単体テストでそれを単独でテストする必要があります。
setup do
@object = Object.new
@object.extend(Greeter)
end
should "greet person" do
@object.stubs(:format).returns("Hello {{NAME}}")
assert_equal "Hello World", @object.greet("World")
end
should "greet person in pirate" do
@object.stubs(:format).returns("Avast {{NAME}} lad!")
assert_equal "Avast Jim lad!", @object.greet("Jim")
end
単体テストが良ければ、それが混在しているモジュールの機能をスモークテストできるはずです。
正しい動作をアサートするテストヘルパーを作成し、混合された各クラスに対してそれを使用します。使用法は次のようになります。
setup do
@object = FooClass.new
end
should_act_as_greeter
単体テストが良ければ、これは期待される動作の簡単なスモークテストであり、適切なデリゲートが呼び出されていることを確認するなどです。
インラインクラスを使用する(要点を示すためだけに、派手なflexmockやスタッバ/モカの使用はしていません)
def test_should_callout_to_foo
m = Class.new do
include ModuleUnderTest
def foo
3
end
end.new
assert_equal 6, m.foo_multiplied_by_two
end
そこにあるモック/スタブライブラリは、これを行うためのよりクリーンな方法を提供するはずです。また、構造体を使用できます:
instance = Struct.new(:foo).new
class<<instance
include ModuleUnderTest
end
instance.foo = 4
多くの場所で使用されているモジュールがある場合、それを行う単体テストがあります(モジュールメソッドの下にテストオブジェクトをスライドさせ、モジュールメソッドがそのオブジェクトで正しく機能するかどうかをテストします)。
minitest
では、各テストは明示的にクラスであるため、テストにモジュールを含めてメソッドをテストできます。
class MyModuleTest < Minitest::Test
include MyModule
def my_module_method_test
# Assert my method works
end
end
私はテストをその特定のクラス/モジュールのコントラクトにのみ焦点を合わせ続けるようにしています。そのモジュールのテストクラスでモジュールの動作を証明した場合(通常、そのモジュールの仕様で宣言されているテストクラスにそのモジュールを含めることにより)、そのモジュールを使用する本番クラスのテストを複製しません。しかし、プロダクションクラスについてテストしたい追加の動作、または統合に関する懸念事項がある場合は、プロダクションクラスのテストを作成します。
たとえば、AttributeValidator
と同様の軽量検証を実行するActiveRecord
というモジュールがあります。モジュール仕様でモジュールの動作のテストを記述します。
before(:each) do
@attribute_validator = TestAttributeValidator.new
end
describe "after set callbacks" do
it "should be invoked when an attribute is set" do
def @attribute_validator.after_set_attribute_one; end
@attribute_validator.should_receive(:after_set_attribute_one).once
@attribute_validator.attribute_one = "asdf"
end
end
class TestAttributeValidator
include AttributeValidator
validating_str_accessor [:attribute_one, /\d{2,5}/]
end
モジュールを含む本番クラスでは、コールバックが行われたことを再度アサートしませんが、インクルードされたクラスには、特定の正規表現を含む特定の検証セットがあり、そのクラスに固有のものであるが、モジュール用に記述したテストを再現します。本番クラスの仕様では、特定の検証が設定されていることを保証したいのですが、検証が一般的に機能するわけではありません。これは一種の統合テストですが、モジュールに対して作成したのと同じアサーションを繰り返さないものです。
describe "ProductionClass validation" do
it "should return true if the attribute is valid" do
@production_class.attribute = @valid_attribute
@production_class.is_valid?.should be_true
end
it "should return false if the attribute is invalid" do
@production_class.attribute = @invalid_attribute
@production_class.is valid?.should be_false
end
end
ここにはいくつかの重複があります(ほとんどの統合テストと同じです)が、テストは2つの異なることを証明します。 1つのテストセットはモジュールの一般的な動作を証明し、もう1つのテストセットはそのモジュールを使用する本番クラスの特定の実装に関する問題を証明します。これらのテストから、モジュールが属性を検証してコールバックを実行することがわかります。また、本番クラスには、本番クラスに固有の特定の基準に対する特定の一連の検証があることがわかります。
お役に立てば幸いです。
私は通常、モジュールをできる限り分離してテストし、基本的にはメソッドをテストし、十分なコード、モック、スタブを使用して機能させます。
次に、モジュールが含まれているクラスのテストも行うでしょう。すべてのクラスをテストするわけではありませんが、十分なカバレッジを得るために十分なクラスをテストし、発生する問題を洞察します。これらのテストでは、モジュールを明示的にテストする必要はありませんが、特定のシナリオでの使用を確実にテストします。
テストの各セットには独自のファイルがあります。
私がしたいのは、新しいHostクラスを作成して、次のようなモジュールをそれにミックスすることです。
describe MyModule do
let(:Host_class) { Class.new { include MyModule } }
let(:instance) { Host_class.new }
describe '#instance_method' do
it 'does something' do
expect(instance.instance_method).to do_something
end
end
end