有没有办法跟踪使用let?
时创建的变量我有一系列测试,其中一些使用let(:server){#blah blah}。部分原因是等待服务器启动,以便在使用之前它处于合适的状态。
当我完成测试时,问题出现了。我想使用server.kill()杀死服务器。如果我可以说出
的效果,这几乎是完美的after(:each) { server.kill }
但这会创建服务器并浪费所有资源/时间来引用它,只有在服务器未在前面的测试中使用时才立即终止它。有没有办法跟踪并只清理服务器?
答案 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块显式启动的,也可以在输出中看到。