(我正在使用 JRuby,但我认为我的问题也适用于 MRI Ruby)
我的 RSpec 定义具有以下总体结构:
RSpec.describe 'XXX' do
before(:all) do
# preparation common for each example
end
after(:all) do
# clean up after each example
end
context 'C1' do
it 'Ex1' do
....
end
it 'Ex2' do
....
end
....
end
context 'C2' do
.... # more examples here
end
结束
这里的问题是准备工作(
before(:all)
)涉及计算一个值,比如说
some_value=MyMod.foo()
我在清理代码中需要它(
after(:all)
),即
MyMod.bar(some_value)
当然,由于
some_value
是一个局部变量,我不能这样做。理论上,我可以使用全局变量,
before(:all) do
$some_value=MyMod.foo()
end
after(:all) do
MyMod.bar($some_value)
end
但这不仅丑陋,如果我决定有一天并行化我的测试,还会导致问题。
这能实现吗?还是我的整体设计有缺陷?
正如 Stefan 在评论中已经指出的,您可以为此使用实例变量。
具体来说,每个 RSpec example group (即
describe
或 context
块)定义一个类,以及任何示例(it
、describe
等)和钩子(before
、after
和组中的around
)在该类实例的上下文中进行评估。
因此,如文档所示,您可以在
before
块中定义实例变量(将属于示例组类的该实例),并从 it
或 after
块中访问它同一组(或在嵌套组中,它们从父组继承钩子),例如像这样:
describe 'something' do
before(:each) do
@some_value = MyMod.foo()
end
after(:each) do
MyMod.bar(@some_value)
end
# Add your examples here...
end
(您还可以在此处使用
before(:all)
和 after(:all)
,在这种情况下,钩子仅针对整个示例组执行一次,而不是在组中的每个示例之前和之后执行。除此之外,它们的工作原理完全相同但请注意,before(:suite)
钩子不能用于定义这样的实例变量,因为它们的计算方式不同,一般情况下,我建议不要使用它们。)
另一种解决方案是使用
let!
定义一个 memoized 辅助方法(因为 !
)在任何示例之前自动评估,并且其保存的值稍后仍然可用:
describe 'something' do
# This is evaluated in an implicit before hook before each example:
let!(:some_value) { MyMod.foo() }
after(:each) do
MyMod.bar(some_value)
end
# Add your examples here...
end
这种方法的一个优点(除了可以说是更干净的语法,更少的
@
符号)是 some_value
不能在示例中重新分配(这可能会导致微妙的错误)。 但是,它的 contents 当然可能仍然包含可变对象,因此这并不能完全保证 after
块中恢复的设置仍然与示例之前的设置相同。
此外,虽然您可以在单个
before
块中设置多个实例变量,但 let!
只允许您定义一个辅助方法。 当然,您始终可以通过以下方式解决该限制:
describe 'something' do
let!(:saved_settings) do
some_value = MyMod.foo()
other_value = MyMod.foo2()
[some_value, other_value].freeze
end
after(:each) do
some_value, other_value = saved_settings
MyMod.bar(some_value)
MyMod.bar2(other_value)
end
end