Rspec let()清理

时间:2013-06-06 20:11:59

标签: ruby rspec

有没有办法跟踪使用let?

时创建的变量

我有一系列测试,其中一些使用let(:server){#blah blah}。部分原因是等待服务器启动,以便在使用之前它处于合适的状态。

当我完成测试时,问题出现了。我想使用server.kill()杀死服务器。如果我可以说出

的效果,这几乎是完美的
after(:each) { server.kill }

但这会创建服务器并浪费所有资源/时间来引用它,只有在服务器未在前面的测试中使用时才立即终止它。有没有办法跟踪并只清理服务器?

4 个答案:

答案 0 :(得分:4)

我遇到过类似的问题。解决此问题的一种简单方法是在let方法中设置实例变量以跟踪对象是否已创建:

describe MyTest do

  before(:each) { @created_server = false }

  let(:server) { 
    @created_server = true
    Server.new 
  }

  after(:each) { server.kill if @created_server }

end

答案 1 :(得分:3)

我会做的是这样的事情:

describe MyTest do
  let(:server) { Server.new }

  context "without server" do
    ## dont kill the server in here.
  end

  context "with server" do

   before do
     server
   end

   after(:each) { server.kill }

   it {}
   it {}
  end
end

答案 2 :(得分:2)

这绝对是一个黑客:

describe "cleanup for let" do
  let(:expensive_object) {
    ExpensiveObject.new
  }
  after(:context) {
    v = __memoized[:expensive_object]
    v.close if v
  }
end

我认为rspec必须在实例可以访问它们的某个地方存储这些惰性值,而__memoized就是那个地方。

有了帮手,它变得有点整洁:

def cleanup(name, &block)
  after(:context) do
    v = __memoized[name]
    instance_exec(v, &block) if v
  end
end

describe "cleanup for let" do
  let(:expensive_object) {
    ExpensiveObject.new
  }
  cleanup(:expensive_object) { |v|
    v.close
  }
end

但仍有改进的余地。我想我宁愿不必两次输入对象的名字,所以这样的东西会更好:

describe "cleanup for let" do
  let(:expensive_object) {
    ExpensiveObject.new
  }.cleanup { |v|
    v.close
  }
end

我不确定如果不破坏rspec就可以做到这一点,但是如果rspec自己看到了它的好处,那么可以在核心中完成某些事情......

编辑:更改为使用instance_exec,因为如果从错误的上下文调用事件,并且将清理更改为after(:context),则rspec开始抱怨,因为显然这是级别这是在回忆。

答案 3 :(得分:0)

只需编写一个小装饰器来处理服务器的显式启动和隐式启动,并允许您确定服务器是否已启动。

想象一下,这是需要启动的真正服务器:

class TheActualServer
  def initialize
    puts 'Server starting'
  end

  def operation1
    1
  end

  def operation2
    2
  end

  def kill
    puts 'Server stopped'
  end
end

可重复使用的装饰器可能如下所示:

class ServiceWrapper < BasicObject
  def initialize(&start_procedure)
    @start_procedure = start_procedure
  end

  def started?
    !!@instance
  end

  def instance
    @instance ||= @start_procedure.call
  end

  alias start instance

  private

  def method_missing(method_name, *arguments)
    instance.public_send(method_name, *arguments)
  end

  def respond_to?(method_name)
    super || instance.respond_to?(method_name)
  end
end

现在,您可以在以下规范中应用此功能:

describe 'something' do
  let(:server) do
    ServiceWrapper.new { TheActualServer.new }
  end

  specify { expect(server.operation1).to eql 1 }
  specify { expect(server.operation2).to eql 2 }
  specify { expect(123).to be_a Numeric }

  context 'when server is running' do
    before(:each) { server.start }

    specify { expect('abc').to be_a String }
    specify { expect(/abc/).to be_a Regexp }
  end

  after(:each) { server.kill if server.started? }
end

当在装饰器上调用一个方法时,它将运行它自己的实现(如果存在)。例如,如果调用#started?,它将回答实际服务器是否已启动。如果它没有自己的方法实现,它会将方法调用委托给它返回的服务器对象。如果它在该点没有引用实际服务器的实例,它将运行提供的start_procedure以获取一个并记住该信息以供将来调用。

如果您将所有发布的代码放入名为server_spec.rb的文件中,则可以使用以下命令运行它:

rspec server_spec.rb

输出将如下:

something
Server starting
Server stopped
  should eql 1
Server starting
Server stopped
  should eql 2
  should be a kind of Numeric
  when server is running
Server starting
Server stopped
    should be a kind of String
Server starting
Server stopped
    should be a kind of Regexp

Finished in 0.00165 seconds (files took 0.07534 seconds to load)
5 examples, 0 failures

请注意,在示例1和2中,调用了服务器上的方法,因此您可以看到装饰器隐式启动的服务器的输出。

在示例3中,根本没有与服务器的交互,因此您无法在日志中看到服务器的输出。

然后在示例4和5中,示例代码中没有与服务器对象直接交互,但是服务器是通过before块显式启动的,也可以在输出中看到。