Rspecでモジュールをテストする際のベストプラクティスは何ですか?いくつかのモデルに含まれるモジュールがいくつかありますが、今のところ、各モデルのテストは重複しています(違いはほとんどありません)。 DRYそれをアップする方法はありますか?
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
Radの方法=>
let(:dummy_class) { Class.new { include ModuleToBeTested } }
または、モジュールでテストクラスを拡張できます。
let(:dummy_class) { Class.new { extend ModuleToBeTested } }
Before(:each)でダミークラスを定義するためにインスタンス変数を使用するよりも 'let'を使用する方が良い
マイクが言ったこと。簡単な例を次に示します。
モジュールコード...
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
単独で、またはクラスをモックすることでテストできるモジュールについては、次のようなものが好きです。
モジュール:
module MyModule
def hallo
"hallo"
end
end
仕様:
describe MyModule do
include MyModule
it { hallo.should == "hallo" }
end
ネストされたサンプルグループをハイジャックするのは間違っているように見えるかもしれませんが、私は簡潔であることが好きです。何かご意見は?
頭の中で、テストスクリプトにダミークラスを作成し、その中にモジュールを含めることができますか?次に、ダミークラスが期待どおりに動作することをテストします。
編集:コメントで指摘されているように、モジュールが混合されたクラスにいくつかの動作が存在することを期待している場合、それらの動作のダミーを実装しようとします。モジュールがその職務を遂行できるようにするだけで十分です。
とはいえ、モジュールがそのHost(「Host」と言いますか?)クラスから多くのことを期待している場合、デザインに少し緊張します。継承ツリーに新しい機能を追加したら、モジュールに期待されるような期待を最小限に抑えようとしていると思います。私の懸念は、私の設計が不快な非柔軟性のいくつかの領域を開発し始めることです。
受け入れられた答えは私が思う正しい答えですが、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
私の最近の仕事、可能な限り少ない配線を使用して
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はそのスコープでは表示されません。
どうですか:
describe MyModule do
subject { Object.new.extend(MyModule) }
it "does stuff" do
expect(subject.does_stuff?).to be_true
end
end
大きくてよく使われるモジュールについては、@ 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
ヘルパータイプも使用できます
# 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
モジュールをスペックファイルに含める必要があります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