在rspec中测试模块

And*_*ius 170 ruby unit-testing rspec

在rspec中测试模块的最佳实践是什么?我有一些模块包含在少数模型中,现在我只是对每个模型进行了重复测试(差异很小).有没有办法让它干涸?

met*_*gfu 209

rad方式=>

let(:dummy_class) { Class.new { include ModuleToBeTested } }
Run Code Online (Sandbox Code Playgroud)

或者,您可以使用模块扩展测试类:

let(:dummy_class) { Class.new { extend ModuleToBeTested } }
Run Code Online (Sandbox Code Playgroud)

使用'let'比使用实例变量在before(:each)中定义虚拟类更好

何时使用RSpec let()?

  • 方式拉.我通常这样做:`let(:class_instance){(Class.new {include Super :: Duper :: Module}).new}`,这样我得到的实例变量最常用于任何方式的测试. (23认同)
  • 甚至是radder:`subject(:instance){Class.new.include(described_class).new}` (5认同)
  • @lulalala不,这是一个超类:http://www.ruby-doc.org/core-2.0.0/Class.html#method-c-new要测试模块,请执行以下操作:`let(:dummy_class) {Class.new {include ModuleToBeTested}}` (3认同)
  • 使用`include`对我来说不起作用,但`extend`做`let(:dummy_class){Class.new {extend ModuleToBeTested}}` (3认同)

小智 109

迈克说的是什么.这是一个简单的例子:

模块代码......

module Say
  def hello
    "hello"
  end
end
Run Code Online (Sandbox Code Playgroud)

规范片段......

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
Run Code Online (Sandbox Code Playgroud)

  • 我编辑的代码更简洁.@dummy_class = Class.new {extend Say}是测试模块所需的全部内容.我怀疑人们会更喜欢这样,因为我们开发人员通常不喜欢输入超过必要的内容. (8认同)
  • 为什么要定义`DummyClass`常量?为什么不只是`@dummy_class = Class.new`?现在,您使用不必要的类定义来污染测试环境.这个DummyClass是为你的每一个规范定义的,在你决定使用相同方法的下一个规范中,重新打开它可能已经包含某些内容的DummyClass定义(尽管在这个简单的例子中,定义在现实生活中是严格空的用例可能会在某些时候添加某些内容然后这种方法变得危险.) (5认同)
  • 你没有在DummyClass声明中包含Say而不是调用`extend`的任何原因? (3认同)
  • grant-birchmeier,他'延伸'到类的实例,即在调用`new`之后.如果你在调用`new`之前这样做,那么你是对的,你会使用`include` (2认同)

小智 30

对于可以单独测试或模拟类的模块,我喜欢以下内容:

模块:

module MyModule
  def hallo
    "hallo"
  end
end
Run Code Online (Sandbox Code Playgroud)

规格:

describe MyModule do
  include MyModule

  it { hallo.should == "hallo" }
end
Run Code Online (Sandbox Code Playgroud)

劫持嵌套示例组可能看起来不对,但我喜欢简洁.有什么想法吗?

  • 可能会弄乱 rspec。我认为使用@metakungfu 描述的`let` 方法更好。 (2认同)

And*_*ius 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
Run Code Online (Sandbox Code Playgroud)

然后,当您需要定义大型和小型版本的行为时,请使用it_should_behave_like()方法引用共享行为.

describe "SmallEdition" do  
  it_should_behave_like "all editions"
  it "should also behave like a small edition" do   
  end 
end
Run Code Online (Sandbox Code Playgroud)


Mik*_*use 21

在我的脑海中,您是否可以在测试脚本中创建一个虚拟类并将模块包含在其中?然后测试虚拟类是否具有您期望的行为.

编辑:如果正如评论中所指出的那样,模块期望某些行为出现在混合的类中,那么我会尝试实现这些行为的假人.足以让模块乐于履行其职责.

也就是说,当一个模块期望从它的主机(我们说"主机"?)类中有很多东西时,我对我的设计有点紧张 - 如果我还没有从基类继承或者不能注入继承树中的新功能然后我想我会尽量减少模块可能具有的任何期望.我担心的是我的设计会开始出现一些令人不愉快的不舒服的方面.


p11*_*00i 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
Run Code Online (Sandbox Code Playgroud)

现在让我们为我们的模块创建规范: 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
Run Code Online (Sandbox Code Playgroud)


All*_*son 8

要测试您的模块,请使用:

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

  # examples
end
Run Code Online (Sandbox Code Playgroud)

要干掉你在多个规范中使用的一些东西,你可以使用共享上下文:

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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)

资源:


Mat*_*lly 6

关于什么:

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


Tim*_*imo 6

我建议,对于更大和使用的模块应该选择"共享实例组"由@Andrius建议在这里.对于你不想经历多个文件等问题的简单的东西,这里是如何确保最大程度地控制你的虚拟东西的可见性(用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
Run Code Online (Sandbox Code Playgroud)


Lei*_*eif 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
Run Code Online (Sandbox Code Playgroud)

我希望

subject {Class.new{include described_class}.new}
Run Code Online (Sandbox Code Playgroud)

工作,但它没有(如在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>
Run Code Online (Sandbox Code Playgroud)

显然,describe_class在该范围内不可见.