web-dev-qa-db-ja.com

Rails:Rubyモジュールのテストをどのように書くのですか?

いくつかのクラスに混在するモジュールの単体テストの記述方法を知りたいのですが、どうすればよいかわかりません。

  1. それらを含むクラスのテストファイルの1つにテストを記述してインスタンスメソッドをテストしますか(正しくないようです)、またはモジュールに固有の別のファイルに含まれているメソッドのテストを保持できますか?

  2. 同じ質問がクラスメソッドにも当てはまります。

  3. 通常のRailsモデルのように、モジュールのクラスごとに個別のテストファイルを用意する必要がありますか、それとも一般モジュールのテストファイルに存在しますか?

49
tsdbrown

私見、モジュールのすべての使用法をカバーする機能テストカバレッジを実行し、単体テストでそれを単独でテストする必要があります。

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

単体テストが良ければ、これは期待される動作の簡単なスモークテストであり、適切なデリゲートが呼び出されていることを確認するなどです。

57
cwninja

インラインクラスを使用する(要点を示すためだけに、派手な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

多くの場所で使用されているモジュールがある場合、それを行う単体テストがあります(モジュールメソッドの下にテストオブジェクトをスライドさせ、モジュールメソッドがそのオブジェクトで正しく機能するかどうかをテストします)。

13
Julik

minitestでは、各テストは明示的にクラスであるため、テストにモジュールを含めてメソッドをテストできます。

class MyModuleTest < Minitest::Test
   include MyModule

   def my_module_method_test
     # Assert my method works
   end
end
4
lcguida

私はテストをその特定のクラス/モジュールのコントラクトにのみ焦点を合わせ続けるようにしています。そのモジュールのテストクラスでモジュールの動作を証明した場合(通常、そのモジュールの仕様で宣言されているテストクラスにそのモジュールを含めることにより)、そのモジュールを使用する本番クラスのテストを複製しません。しかし、プロダクションクラスについてテストしたい追加の動作、または統合に関する懸念事項がある場合は、プロダクションクラスのテストを作成します。

たとえば、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つのテストセットはそのモジュールを使用する本番クラスの特定の実装に関する問題を証明します。これらのテストから、モジュールが属性を検証してコールバックを実行することがわかります。また、本番クラスには、本番クラスに固有の特定の基準に対する特定の一連の検証があることがわかります。

お役に立てば幸いです。

4
Dave Sims

私は通常、モジュールをできる限り分離してテストし、基本的にはメソッドをテストし、十分なコード、モック、スタブを使用して機能させます。

次に、モジュールが含まれているクラスのテストも行うでしょう。すべてのクラスをテストするわけではありませんが、十分なカバレッジを得るために十分なクラスをテストし、発生する問題を洞察します。これらのテストでは、モジュールを明示的にテストする必要はありませんが、特定のシナリオでの使用を確実にテストします。

テストの各セットには独自のファイルがあります。

3
Toby Hede

私がしたいのは、新しい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
2