Maybe it is just me, but I had suspected some weirdness here and there from using the fancy “let(:var_sym)” syntax. The trusty RSpec book says:
The first call to let( ) defines a memoized output( ) method that returns
a double object. Memoized means that the first time the method is
invoked, the return value is cached and that same value is returned
every subsequent time the method is invoked within the same scope.
So, it would seem that let() is a great way to define an object once, and use it from there onward.
However, I saw that in this particular instance of running an “expensive” operation in the let block, it took 17 seconds instead of 7 seconds to run the specs! I could see my specs ticking along, very slowly, one at a time. What the heck? I asked myself. Is there something that says “turn caching off (or on)?”
Fancy Schmancy! To save ~10 seconds, I’ll forgo the niceties of let() and revert to using the @var_name syntax.
Given the following RSpec code:
context 'instance methods' do let(:sample_xml_file) {File.expand_path('../../data/sample_v_1_13.xml', __FILE__)} let(:p) { xml_str = File.read(sample_xml_file) Nemsis::Parser.new(xml_str) } let(:r) {Nemsis::Renderer::HTML.new(p)} describe '#render_html' do context "plain HTML" do let(:html) { r.render(false) } it 'returns not nil' do html.should_not be_nil end it 'has title section' do html.should =~ ... end context 'specialty patient section' do it('has specialty patient') { html.should =~ ... } it('has specialty patient trauma criteria') { html.should =~ ... } it('has specialty patient airway') { html.should =~ ... } end it "should not have a STYLE section" do html.should_not =~ ... end it "write to html file" do write_html_file(sample_xml_file, "simple", html) end end context "fancy HTML" do let(:html) { r.render(true) } it "should have a STYLE section" do html.should =~ ... end it "write to html file" do write_html_file(sample_xml_file, "fancy", html) end end end ...
Contrast the above with the more traditional approach that uses a before block and @variables:
context 'instance methods' do before :all do @sample_xml_file = File.expand_path('../../data/sample_v_1_13.xml', __FILE__) xml_str = File.read(@sample_xml_file) p = Nemsis::Parser.new(xml_str) r = Nemsis::Renderer::HTML.new(p) @html = r.render(false) end describe '#render_html' do context "plain HTML" do it 'returns not nil' do @html.should_not be_nil end
I did a bit more formal timing, which revealed the truth:
- let() — 10.7 seconds
- before block — 2.4 seconds
Am I missing something?
Yes, you are missing something. let() memoizes per example, whereas before(:all) runs once per group. I’ll beef up the rdoc to clarify this.
Yea, I guess when I saw
let()
at the top of the Codebreaker example, that the items would be defined once, and then used in each of the two ensuing “it should” examples.So is this a good rule of thumb: “if the let() block is computationally expensive, use a before block instead?”
Pingback: 当使用RSpec let()? – CodingBlog
Pingback: When to use RSpec let()? - ExceptionsHub
Pingback: [ruby] RSpec let ()을 언제 사용합니까? - 리뷰나라