context "foo" do
let(:params) do
{ :foo => foo, :bar => "bar" }
end
let(:foo) { "foo" }
it "is set to foo" do
params[:foo].should eq("foo")
end
context "when foo is bar" do
let(:foo) { "bar" }
# NOTE we didn't have to redefine params entirely!
it "is set to bar" do
params[:foo].should eq("bar")
end
end
end
let!(:country) { create(:country) }
let(:country_id) { country.id }
before { get "api/countries/#{country_id}" }
it 'responds with HTTP 200' { should respond_with(200) }
context 'when the country does not exist' do
let(:country_id) { -1 }
it 'responds with HTTP 404' { should respond_with(404) }
end
这使规格清晰可读 .
13
"before"默认隐含 before(:each) . Ref The Rspec Book,copyright 2010,page 228 .
before(scope = :each, options={}, &block)
我使用 before(:each) 为每个示例组播种一些数据,而不必调用 let 方法在"it"块中创建数据 . 在这种情况下,"it"块中的代码更少 .
9 回答
重要的是要记住 let 是惰性评估的,不会在其中放置副作用方法,否则您将无法轻易地从 let 更改为 before(:each) . 您可以使用 let! 而不是 let ,以便在每个方案之前对其进行评估 .
使用实例变量和
let()
之间的区别在于let()
是 lazy-evaluated . 这意味着let()
在第一次运行它定义的方法之前不会被评估 .before
和let
之间的区别在于let()
为您提供了一种以'cascading'样式定义一组变量的好方法 . 通过这样做,通过简化代码,规范看起来更好一些 .约瑟夫的注意事项 - 如果你在
before(:all)
中创建数据库对象,他们更有可能在你的测试数据库中留下瑕疵 . 请改用before(:each)
.使用let及其懒惰评估的另一个原因是,你可以通过覆盖上下文中的let来获取一个复杂的对象并测试单个部分,就像在这个非常人为的例子中一样:
let是功能性的,因为它本质上是一个Proc . 也是它的缓存 .
我得到的一个问题就是......在一个正在评估变化的Spec块中 .
您需要确保在预期阻止之外调用
let
. 即你在let块中调用FactoryGirl.create
. 我通常通过验证对象是否持久来做到这一点 .否则,当第一次调用
let
块时,由于延迟实例化而实际发生数据库更改 .Update
只需添加备注 . 小心玩code golf或在这种情况下使用这个答案的rspec golf .
在这种情况下,我只需要调用一个对象响应的方法 . 所以我在对象上调用
_.persisted?
_方法作为它的真实 . 我所有的测试,但通过调用它来使对象栩栩如生 .所以你无法重构
成为
因为对象尚未实例化...它的懒惰 . :)
Update 2
利用let! syntax来创建即时对象,这应该完全避免这个问题 . 注意虽然它会打败很多非撞击让懒惰的目的 .
此外,在某些情况下,您可能实际上想要利用subject syntax而不是let,因为它可能会为您提供其他选项 .
我使用
let
使用上下文在我的API规范中测试我的HTTP 404响应 .要创建资源,我使用
let!
. 但是为了存储资源标识符,我使用let
. 看看它的样子:这使规格清晰可读 .
"before"默认隐含
before(:each)
. Ref The Rspec Book,copyright 2010,page 228 .我使用
before(:each)
为每个示例组播种一些数据,而不必调用let
方法在"it"块中创建数据 . 在这种情况下,"it"块中的代码更少 .如果我想在某些示例中使用某些数据而不是其他示例,则使用
let
.之前和之前都非常适合干掉“它”块 .
为避免混淆,"let"与
before(:all)
不同 . "Let"重新评估每个示例的方法和值("it"),但在同一示例中将值缓存到多个调用中 . 你可以在这里阅读更多相关信息:https://www.relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/let-and-let通常,
let()
是一种更好的语法,它可以节省您在整个地方键入@name
符号 . 但是,请注意!我发现let()
也引入了微妙的错误(或者至少是头部划痕)因为变量在您尝试使用之前并不存在...告诉故事标志:如果在let()
之后添加puts
以查看变量是否正确允许规范通过,但没有puts
规范失败 - 你已经发现了这个微妙之处 .我还发现
let()
似乎并没有在所有情况下缓存!我在我的博客中写了:http://technicaldebt.com/?p=1242也许只是我?
我在我的rspec测试中完全替换了实例变量的所有用法以使用let() . 我为一个朋友写了一个简短的例子,用它来教一个小的Rspec类:http://ruby-lambda.blogspot.com/2011/02/agile-rspec-with-let.html
正如其他一些答案所说的那样,let()是惰性求值的,因此它只会加载需要加载的那些 . 它会破坏规范并使其更具可读性 . 事实上,我已经将我的控制器中使用的Rspec let()代码移植到了inherited_resource gem的样式中 . http://ruby-lambda.blogspot.com/2010/06/stealing-let-from-rspec.html
除了懒惰的评估,另一个优点是,结合ActiveSupport :: Concern,以及load-everything-in规范/支持/行为,您可以创建特定于您的应用程序的自己的spec mini-DSL . 我已经编写过针对Rack和RESTful资源进行测试的文章 .
我使用的策略是Factory-everything(通过Machinist Forgery / Faker) . 但是,可以使用它与before(:each)块一起为整个示例组预加载工厂,允许规范更快地运行:http://makandra.com/notes/770-taking-advantage-of-rspec-s-let-in-before-blocks
出于以下几个原因,我总是更喜欢
let
到实例变量:实例变量在引用时才会存在 . 这意味着如果你手指操作实例变量的拼写,将创建一个新的并初始化为
nil
,这可能导致细微的错误和误报 . 由于let
创建了一个方法,当你拼错它时你会得到NameError
,我认为这更好 . 它也使得重构规范变得更容易 .一个
before(:each)
钩子将在每个例子之前运行,即使这个例子通常不是很重要,但如果实例变量的设置需要很长时间,那么你就是在浪费周期 . 对于let
定义的方法,初始化代码仅在示例调用它时运行 .您可以直接将示例中的局部变量重构为let而不更改示例中的引用语法 . 如果重构实例变量,则必须更改示例中引用对象的方式(例如,添加
@
) .这有点主观,但正如迈克刘易斯指出的那样,我认为它使规范更容易阅读 . 我喜欢用
let
定义所有依赖对象的组织,并保持我的it
块好看又短 .