web-dev-qa-db-ja.com

rspecでのモジュールのテスト

Rspecでモジュールをテストする際のベストプラクティスは何ですか?いくつかのモデルに含まれるモジュールがいくつかありますが、今のところ、各モデルのテストは重複しています(違いはほとんどありません)。 DRYそれをアップする方法はありますか?

167
Andrius

Rspecホームページでより良い解決策を見つけました。どうやら共有サンプルグループをサポートしているようです。から https://www.relishapp.com/rspec/rspec-core/v/2-13/docs/example-groups/shared-examples

共有サンプルグループ

共有サンプルグループを作成し、それらのグループを他のグループに含めることができます。

製品のすべてのエディション(大小両方)に適用される動作があるとします。

まず、「共有」動作を考慮します。

shared_examples_for "all editions" do   
  it "should behave like all editions" do   
  end 
end

次に、大規模エディションと小規模エディションの動作を定義する必要がある場合は、it_should_behave_like()メソッドを使用して共有動作を参照します。

describe "SmallEdition" do  
  it_should_behave_like "all editions"
  it "should also behave like a small edition" do   
  end 
end
23
Andrius

Radの方法=>

let(:dummy_class) { Class.new { include ModuleToBeTested } }

または、モジュールでテストクラスを拡張できます。

let(:dummy_class) { Class.new { extend ModuleToBeTested } }

Before(:each)でダミークラスを定義するためにインスタンス変数を使用するよりも 'let'を使用する方が良い

RSpecを使用する場合let()?

202
metakungfu

マイクが言ったこと。簡単な例を次に示します。

モジュールコード...

module Say
  def hello
    "hello"
  end
end

スペックフラグメント...

class DummyClass
end

before(:each) do
  @dummy_class = DummyClass.new
  @dummy_class.extend(Say)
end

it "get hello string" do
  expect(@dummy_class.hello).to eq "hello"
end
108
Karmen Blake

単独で、またはクラスをモックすることでテストできるモジュールについては、次のようなものが好きです。

モジュール:

module MyModule
  def hallo
    "hallo"
  end
end

仕様:

describe MyModule do
  include MyModule

  it { hallo.should == "hallo" }
end

ネストされたサンプルグループをハイジャックするのは間違っているように見えるかもしれませんが、私は簡潔であることが好きです。何かご意見は?

29

頭の中で、テストスクリプトにダミークラスを作成し、その中にモジュールを含めることができますか?次に、ダミークラスが期待どおりに動作することをテストします。

編集:コメントで指摘されているように、モジュールが混合されたクラスにいくつかの動作が存在することを期待している場合、それらの動作のダミーを実装しようとします。モジュールがその職務を遂行できるようにするだけで十分です。

とはいえ、モジュールがそのHost(「Host」と言いますか?)クラスから多くのことを期待している場合、デザインに少し緊張します。継承ツリーに新しい機能を追加したら、モジュールに期待されるような期待を最小限に抑えようとしていると思います。私の懸念は、私の設計が不快な非柔軟性のいくつかの領域を開発し始めることです。

21
Mike Woodhouse

受け入れられた答えは私が思う正しい答えですが、rpsecs shared_examples_forおよびit_behaves_likeメソッドの使用方法の例を追加したいと思いました。コードスニペットでいくつかのトリックに言及していますが、詳細については、これを参照してください relishapp-rspec-guide

これにより、モジュールを含むクラスのいずれかでモジュールをテストできます。 あなたは実際にアプリケーションで使用しているものをテストしています

例を見てみましょう:

# Lets assume a Movable module
module Movable
  def self.movable_class?
    true
  end

  def has_feets?
    true
  end
end

# Include Movable into Person and Animal
class Person < ActiveRecord::Base
  include Movable
end

class Animal < ActiveRecord::Base
  include Movable
end

モジュールの仕様を作成しましょう:movable_spec.rb

shared_examples_for Movable do
  context 'with an instance' do
    before(:each) do
      # described_class points on the class, if you need an instance of it: 
      @obj = described_class.new

      # or you can use a parameter see below Animal test
      @obj = obj if obj.present?
    end

    it 'should have feets' do
      @obj.has_feets?.should be_true
    end
  end

  context 'class methods' do
    it 'should be a movable class' do
      described_class.movable_class?.should be_true
    end
  end
end

# Now list every model in your app to test them properly

describe Person do
  it_behaves_like Movable
end

describe Animal do
  it_behaves_like Movable do
    let(:obj) { Animal.new({ :name => 'capybara' }) }
  end
end
10
p1100i

私の最近の仕事、可能な限り少ない配線を使用して

require 'spec_helper'

describe Module::UnderTest do
  subject {Object.new.extend(described_class)}

  context '.module_method' do
    it {is_expected.to respond_to(:module_method)}
    # etc etc
  end
end

私は望む

subject {Class.new{include described_class}.new}

動作しましたが、動作しません(Ruby MRI 2.2.3およびRSpec :: Core 3.3.0)

Failure/Error: subject {Class.new{include described_class}.new}
  NameError:
    undefined local variable or method `described_class' for #<Class:0x000000063a6708>

明らかにdescribe_classはそのスコープでは表示されません。

6
Leif

どうですか:

describe MyModule do
  subject { Object.new.extend(MyModule) }
  it "does stuff" do
    expect(subject.does_stuff?).to be_true
  end
end
6
Matt Connolly

大きくてよく使われるモジュールについては、@ Andrius here で提案されている「共有サンプルグループ」を選択することをお勧めします。複数のファイルがあるなどのトラブルを避けたい単純なものについては、ダミーのものの可視性を最大限に制御する方法を以下に示します(rspec 2.14.6でテストし、コードをコピーして貼り付けます) specファイルを実行します):

module YourCoolModule
  def your_cool_module_method
  end
end

describe YourCoolModule do
  context "cntxt1" do
    let(:dummy_class) do
      Class.new do
        include YourCoolModule

        #Say, how your module works might depend on the return value of to_s for
        #the extending instances and you want to test this. You could of course
        #just mock/stub, but since you so conveniently have the class def here
        #you might be tempted to use it?
        def to_s
          "dummy"
        end

        #In case your module would happen to depend on the class having a name
        #you can simulate that behaviour easily.
        def self.name
          "DummyClass"
        end
      end
    end

    context "instances" do
      subject { dummy_class.new }

      it { subject.should be_an_instance_of(dummy_class) }
      it { should respond_to(:your_cool_module_method)}
      it { should be_a(YourCoolModule) }
      its (:to_s) { should eq("dummy") }
    end

    context "classes" do
      subject { dummy_class }
      it { should be_an_instance_of(Class) }
      it { defined?(DummyClass).should be_nil }
      its (:name) { should eq("DummyClass") }
    end
  end

  context "cntxt2" do
    it "should not be possible to access let methods from anohter context" do
      defined?(dummy_class).should be_nil
    end
  end

  it "should not be possible to access let methods from a child context" do
    defined?(dummy_class).should be_nil
  end
end

#You could also try to benefit from implicit subject using the descbie
#method in conjunction with local variables. You may want to scope your local
#variables. You can't use context here, because that can only be done inside
#a describe block, however you can use Porc.new and call it immediately or a
#describe blocks inside a describe block.

#Proc.new do
describe "YourCoolModule" do #But you mustn't refer to the module by the
  #constant itself, because if you do, it seems you can't reset what your
  #describing in inner scopes, so don't forget the quotes.
  dummy_class = Class.new { include YourCoolModule }
  #Now we can benefit from the implicit subject (being an instance of the
  #class whenever we are describing a class) and just..
  describe dummy_class do
    it { should respond_to(:your_cool_module_method) }
    it { should_not be_an_instance_of(Class) }
    it { should be_an_instance_of(dummy_class) }
    it { should be_a(YourCoolModule) }
  end
  describe Object do
    it { should_not respond_to(:your_cool_module_method) }
    it { should_not be_an_instance_of(Class) }
    it { should_not be_an_instance_of(dummy_class) }
    it { should be_an_instance_of(Object) }
    it { should_not be_a(YourCoolModule) }
  end
#end.call
end

#In this simple case there's necessarily no need for a variable at all..
describe Class.new { include YourCoolModule } do
  it { should respond_to(:your_cool_module_method) }
  it { should_not be_a(Class) }
  it { should be_a(YourCoolModule) }
end

describe "dummy_class not defined" do
  it { defined?(dummy_class).should be_nil }
end
6
Timo

ヘルパータイプも使用できます

# api_helper.rb
module Api
  def my_meth
    10
  end
end
# spec/api_spec.rb
require "api_helper"

RSpec.describe Api, :type => :helper do
  describe "#my_meth" do
    it { expect( helper.my_meth ).to eq 10 }
  end
end

ドキュメントは次のとおりです。 https://www.relishapp.com/rspec/rspec-Rails/v/3-3/docs/helper-specs/helper-spec

4
Uri

モジュールをスペックファイルに含める必要がありますmudule Test module MyModule def test 'test' end end endスペックファイルRSpec.describe Test::MyModule do include Test::MyModule #you can call directly the method *test* it 'returns test' do expect(test).to eql('test') end end

0
mdlx