Когда использовать RSpec let ()?

Я обычно использую перед блоками для установки переменных экземпляра. Затем я использую эти переменные в своих примерах. Я недавно наткнулся let(). Согласно документам RSpec, он используется для

... чтобы определить memoized вспомогательный метод. Значение будет кэшироваться в нескольких вызовах в одном примере, но не в примерах.

чем это отличается от использования переменных экземпляра в блоках before? А также Когда вы должны использовать let() vs before()?

9 ответов


Я всегда предпочитаю let переменной экземпляра по нескольким причинам:

  • переменные экземпляра возникают при ссылке. Это означает, что если вы жирным пальцем написание переменной экземпляра, новый будет создан и инициализирован в nil, что может привести к тонким ошибкам и ложным срабатываниям. С let создает метод, вы получите NameError когда вы неправильно пишете, что я нахожу предпочтительным. Это упрощает рефакторинг спецификаций, тоже.
  • A before(:each) hook будет выполняться перед каждым примером, даже если в Примере не используется ни одна из переменных экземпляра, определенных в hook. Обычно это не имеет большого значения, но если настройка переменной экземпляра занимает много времени, то вы тратите циклы. Для метода, определенного let, код инициализации запускается только в том случае, если пример вызывает его.
  • вы можете выполнить рефакторинг из локальной переменной в пример непосредственно в давай без изменения ссылающийся синтаксис в Примере. Если вы рефакторинг к переменной экземпляра, вы должны изменить как вы ссылаетесь на объект в примере (например, добавить @).
  • это немного субъективно, но, как отметил Майк Льюис, Я думаю, что это облегчает чтение спецификации. Мне нравится организация определения всех моих зависимых объектов с помощью let и я it блок хороший и короткий.

разница между использованием переменных экземпляров и let() Это let() is лентяй-оценка. Это значит, что let() не вычисляется, пока метод, который он определяет, не запускается в первый раз.

разницу между before и let это let() дает вам хороший способ определения группы переменных в "каскадной" стиль. Делая это, спецификация выглядит немного лучше, упрощая код.


Я полностью заменил все использование переменных экземпляра в моих тестах 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/support/ behavior вы можете создать свой собственный spec mini-DSL, специфичный для вашего приложения. Я написал их для тестирования против Rack и RESTful ресурсов.

стратегия, которую я использую Фабрика-все (через машиниста+подделка / подделка). Однако его можно использовать в сочетании с блоками before(:each) для предварительной загрузки фабрик для всего набора групп примеров, что позволяет спецификации работать быстрее: http://makandra.com/notes/770-taking-advantage-of-rspec-s-let-in-before-blocks


важно иметь в виду, что пусть лениво оценивается и не ставит методы побочных эффектов в нем, иначе вы не смогли бы изменить с пусть to before (: each) легко. Вы можете использовать давайте! вместо пусть чтобы он оценивался перед каждым сценарием.


в общем, let() - более приятный синтаксис, и это экономит ввод @name символы повсюду. Но,будьте осторожны emptor! Я нашел let() также вводит тонкие ошибки (или, по крайней мере, царапание головы), потому что переменная не существует, пока вы не попытаетесь ее использовать... Tell tale знак: если добавить puts после let() чтобы увидеть, что переменная верна, позволяет спецификации проходить, но без puts спецификация не работает - вы нашли это хитрость.

Я также обнаружил, что let() похоже, не кэшируется при всех обстоятельствах! Я написал это в своем блоге:http://technicaldebt.com/?p=1242

может быть, это только у меня?


давайте функциональна, как ее по существу Тез.Докл. Также его кэшируется.

один попался я нашел сразу с let... В блоке спецификаций, который оценивает изменение.

let(:object) {FactoryGirl.create :object}

expect {
  post :destroy, id: review.id
}.to change(Object, :count).by(-1)

вам нужно обязательно позвонить let вне вашего блока ожидания. то есть вы звоните FactoryGirl.create в вашем блоке let. Обычно я делаю это, проверяя, что объект сохраняется.

object.persisted?.should eq true

в противном случае, когда let блок вызывается при первом изменении базы данных на самом деле это происходит из-за ленивой инстанциации.

обновление

просто добавление заметки. Будьте осторожны, играя гольф-кода или в этом случае RSpec golf с этим ответом.

в этом случае мне просто нужно вызвать какой-то метод, на который отвечает объект. Поэтому я призываю _.persisted?_ метод на объекте как его истинность. Все, что я пытаюсь сделать, это создать экземпляр объекта. Ты можешь позвонить пустой? или шь? тоже. Это не Тест, А чего объект жизни, называя его.

так что вы не можете выполнить рефакторинг

object.persisted?.should eq true

на

object.should be_persisted 

поскольку объект не был создан... это лень. :)

обновление 2

использовать давайте! синтаксис для мгновенного создания объекта, который должен полностью избежать этой проблемы. Обратите внимание, хотя это будет победить много цели лени не грохнули пусть.

также в некоторых случаях возможно, вы действительно захотите использовать тема синтаксис вместо let, поскольку это может дать вам дополнительные опции.

subject(:object) {FactoryGirl.create :object}

Примечание Для Джозефа -- если вы создаете объекты базы данных в before(:all) они не будут захвачены в транзакции, и вы с большей вероятностью оставите cruft в своей тестовой базе данных. Использовать before(:each) вместо.

другой причиной использования let и его ленивой оценки является то, что вы можете взять сложный объект и проверить отдельные части, переопределив lets в контекстах, как в этом очень надуманном примере:

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

"до" по умолчанию подразумевает before(:each). Ref The Rspec Book, copyright 2010, page 228.

before(scope = :each, options={}, &block)

Я использую before(:each) для заполнения некоторых данных для каждой группы примеров без вызова let метод для создания данных в блоке" it". В этом случае меньше кода в блоке "it".

Я использую let если мне нужны некоторые данные в некоторых примерах, но не другие.

оба до и пусть отлично подходят для высыхания блоков "it".

чтобы избежать каких-либо путаница, "пусть" не то же самое, что before(:all). "Let" повторно оценивает свой метод и значение для каждого примера ("it"), но кэширует значение в нескольких вызовах в том же примере. Подробнее об этом можно прочитать здесь:https://www.relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/let-and-let


Я использую let чтобы проверить мои ответы HTTP 404 в моих спецификациях API, используя контексты.

создать ресурс, я использую let!. Но для хранения идентификатора ресурса я использую let. Посмотрите, как это выглядит:

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

это сохраняет спецификации чистыми и читаемыми.