Когда использовать 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
это сохраняет спецификации чистыми и читаемыми.