rspec에서 모듈 테스트


175

rspec에서 모듈 테스트에 대한 모범 사례는 무엇입니까? 몇 가지 모델에 포함되는 일부 모듈이 있으며 현재로서는 각 모델에 대해 중복 테스트가 있습니다 (차이가 거의 없음). 그것을 건조시키는 방법이 있습니까?

답변:


219

rad way =>

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

또는 모듈을 사용하여 테스트 클래스를 확장 할 수 있습니다.

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

'let'을 사용하는 것은 before (: each)에서 더미 클래스를 정의하기 위해 인스턴스 변수를 사용하는 것보다 낫습니다.

RSpec let ()을 언제 사용합니까?


1
좋은. 이를 통해 클래스 ivar 스패닝 테스트와 관련된 모든 종류의 문제를 피할 수있었습니다. 상수에 할당하여 클래스 이름을 부여하십시오.
captainpete

3
@lulalala 아니오, 그것은 슈퍼 클래스입니다 : ruby-doc.org/core-2.0.0/Class.html#method-c-new 모듈을 테스트하려면 다음과 같이하십시오 :let(:dummy_class) { Class.new { include ModuleToBeTested } }
Timo

26
웨이 라드 나는 보통 다음 let(:class_instance) { (Class.new { include Super::Duper::Module }).new }과 같이한다 : , 그런 식으로 테스트하는 데 가장 자주 사용되는 인스턴스 변수를 얻는다.
Automatico

3
사용 include나를 위해 작동하지 않고 extend않습니다let(:dummy_class) { Class.new { extend ModuleToBeTested } }
마이크 W

8
심지어 레이더 :subject(:instance) { Class.new.include(described_class).new }
Richard-Degenne

108

마이크가 말한 것. 다음은 간단한 예입니다.

모듈 코드 ...

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

3
include SayDummyClass 선언에 포함 되지 않은 이유는 extend무엇입니까?
그랜트 버치 마이어

2
grant-birchmeier, 그는 extend클래스의 인스턴스에, 즉 new호출 된 후 . 이전에이 작업을 수행 한 적이 있으면 new바로 사용하실 수 있습니다.include
Hedgehog

8
더 간결하게 코드를 편집했습니다. @dummy_class = Class.new {extend Say} 만 있으면 모듈을 테스트 할 수 있습니다. 우리 개발자들이 종종 필요 이상으로 입력하는 것을 좋아하지 않기 때문에 사람들이 선호한다고 생각합니다.
Tim Harper

@TimHarper 시도했지만 인스턴스 메소드는 클래스 메소드가되었습니다. 생각?
lulalala

6
DummyClass상수를 왜 정의 하시겠습니까? 왜 안돼 @dummy_class = Class.new? 이제 불필요한 클래스 정의로 테스트 환경을 오염시킵니다. 이 DummyClass는 모든 스펙과 각 스펙에 대해 정의되며 다음 스펙에서 동일한 접근 방식을 사용하고 DummyClass 정의를 다시 열기로 결정한 경우 이미 포함되어있을 수 있습니다 (사소한 예에서는 정의가 실제로 비어 있음) 유스 케이스 어떤 시점에서 무언가가 추가
Timo

29

개별적으로 테스트하거나 클래스를 조롱하여 테스트 할 수있는 모듈의 경우 다음 라인을 따라 무언가를 좋아합니다.

기준 치수:

module MyModule
  def hallo
    "hallo"
  end
end

투기:

describe MyModule do
  include MyModule

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

중첩 된 예제 그룹을 가로채는 것은 잘못된 것처럼 보이지만 간결함을 좋아합니다. 이견있는 사람?


1
나는 이것을 좋아한다, 그것은 매우 간단하다.
iain

2
rspec을 망칠 수 있습니다. let@metakungfu가 설명 하는 방법을 사용하는 것이 좋습니다.
Automatico

@ Cort3z 메소드 이름이 충돌하지 않도록해야합니다. 일이 정말 간단 할 때만이 방법을 사용하고 있습니다.
Frank C. Schuetz

이름 충돌로 인해 테스트 스위트가 엉망이되었습니다.
roxxypoxxy

24

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

Large 및 Small 에디션의 동작을 정의해야하는 경우 it_should_behave_like () 메소드를 사용하여 공유 동작을 참조하십시오.

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


21

내 머리 위로 테스트 스크립트에 더미 클래스를 만들고 모듈을 포함시킬 수 있습니까? 그런 다음 더미 클래스가 예상 한대로 동작하는지 테스트하십시오.

편집 : 의견에서 지적했듯이 모듈이 혼합 된 클래스에 일부 동작이 나타날 것으로 예상되면 해당 동작의 인형을 구현하려고합니다. 모듈이 임무를 수행하기에 만족할만큼 충분합니다.

즉, 모듈이 호스트에서 많은 것을 기대할 때 (내가 "host"라고 말합니까?) 클래스에서 디자인에 대해 약간 긴장할 것입니다. 상속 트리에 새로운 기능을 제공하면 모듈이 가질 수있는 기대치를 최소화하려고 노력하고 있다고 생각합니다. 내 디자인이 불쾌한 융통성이없는 일부 영역을 개발하기 시작한다는 것이 나의 관심사입니다.


모듈이 특정 속성과 동작을 가진 클래스에 의존하는 경우 어떻게해야합니까?
Andrius

10

받아 들인 대답은 내가 생각하는 정답이지만 rpsecs shared_examples_forit_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

6

이건 어떤가요:

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

6

더 크고 많이 사용되는 모듈의 경우 @Andrius가 제안한 "공유 예제 그룹"을 선택해야한다고 제안 합니다 . 여러 파일 등의 문제를 겪고 싶지 않은 간단한 것들을 위해 더미 물건의 가시성을 최대한 제어하는 ​​방법은 다음과 같습니다 (rspec 2.14.6으로 테스트 됨). 사양 파일 및 실행) :

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

어떤 이유로 든 subject { dummy_class.new }작동합니다. 와 함께 사건이 subject { dummy_class }나를 위해 작동하지 않습니다.
valk

6

가능한 한 적은 배선을 사용하는 나의 최근 작품

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>

분명히 description_class는 해당 범위에서 보이지 않습니다.


6

모듈을 테스트하려면 다음을 사용하십시오.

describe MyCoolModule do
  subject(:my_instance) { Class.new.extend(described_class) }

  # examples
end

여러 사양에서 사용하는 것을 건조시키기 위해 공유 컨텍스트를 사용할 수 있습니다.

RSpec.shared_context 'some shared context' do
  let(:reused_thing)       { create :the_thing }
  let(:reused_other_thing) { create :the_thing }

  shared_examples_for 'the stuff' do
    it { ... }
    it { ... }
  end
end
require 'some_shared_context'

describe MyCoolClass do
  include_context 'some shared context'

  it_behaves_like 'the stuff'

  it_behaves_like 'the stuff' do
    let(:reused_thing) { create :overrides_the_thing_in_shared_context }
  end
end

자원:



0

스펙 파일의 스펙 파일 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


-1

클래스에 독립적 인 모듈 방법을 테스트하는 하나의 가능한 솔루션

module moduleToTest
  def method_to_test
    'value'
  end
end

그리고 사양

describe moduleToTest do
  let(:dummy_class) { Class.new { include moduleToTest } }
  let(:subject) { dummy_class.new }

  describe '#method_to_test' do
    it 'returns value' do
      expect(subject.method_to_test).to eq('value')
    end
  end
end

그리고 그것들을 DRY 테스트하고 싶다면 shared_examples 가 좋은 접근법입니다.


나는 당신을 억압 한 사람이 아니지만 두 개의 LET을로 바꾸는 것이 좋습니다 subject(:module_to_test_instance) { Class.new.include(described_class) }. 그렇지 않으면 나는 당신의 대답에 실제로 아무것도 잘못 보지 않습니다.
Allison

-1

하나 이상의 모듈을 테스트해야하므로 반복되는 패턴입니다. 이러한 이유로이를 위해 도우미를 만드는 것이 바람직합니다.

나는 그것을하는 방법을 설명하는 이 게시물 을 찾았 지만 사이트가 언젠가 중단 될 수 있기 때문에 여기에서 대처하고 있습니다.

이것은 객체 인스턴스가 인스턴스 메소드를 구현하지 않도록하는 것입니다 : 클래스 에서 allow메소드를 시도 할 때 발생 하는 오류 dummy.

암호:

spec/support/helpers/dummy_class_helpers.rb

module DummyClassHelpers

  def dummy_class(name, &block)
    let(name.to_s.underscore) do
      klass = Class.new(&block)

      self.class.const_set name.to_s.classify, klass
    end
  end

end

spec/spec_helper.rb

# skip this if you want to manually require
Dir[File.expand_path("../support/**/*.rb", __FILE__)].each {|f| require f}

RSpec.configure do |config|
  config.extend DummyClassHelpers
end

귀하의 사양에서 :

require 'spec_helper'

RSpec.shared_examples "JsonSerializerConcern" do

  dummy_class(:dummy)

  dummy_class(:dummy_serializer) do
     def self.represent(object)
     end
   end

  describe "#serialize_collection" do
    it "wraps a record in a serializer" do
      expect(dummy_serializer).to receive(:represent).with(an_instance_of(dummy)).exactly(3).times

      subject.serialize_collection [dummy.new, dummy.new, dummy.new]
    end
  end
end
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.