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

427

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

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

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

  • 1
    Пусть блоки лениво оцениваются, пока перед каждым примером выполняются блоки (они в целом медленнее). Использование блоков before зависит от личных предпочтений (стиль кодирования, макеты / заглушки ...). Пусть блоки обычно предпочтительнее. Вы можете проверить более подробную информацию о лете
Теги:
rspec
tdd
bdd

9 ответов

581
Лучший ответ

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

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

Связанную ссылку можно найти здесь: http://www.betterspecs.org/#let

  • 2
    Мне действительно нравится первое упомянутое вами преимущество, но не могли бы вы объяснить немного больше о третьем? До сих пор в примерах, которые я видел (спецификации mongoid: github.com/mongoid/mongoid/blob/master/spec/functional/mongoid/… ), используются однострочные блоки, и я не вижу, как отсутствие "@" делает это легче читать.
  • 6
    Как я уже сказал, это немного субъективно, но я считаю полезным использовать let для определения всех зависимых объектов и использовать before(:each) для настройки необходимой конфигурации или любых макетов / заглушек, необходимых в примерах. Я предпочитаю это одному большому крючку, содержащему все это. Кроме того, let(:foo) { Foo.new } менее шумный (и более let(:foo) { Foo.new } ), чем before(:each) { @foo = Foo.new } . Вот пример того, как я его использую: github.com/myronmarston/vcr/blob/v1.7.0/spec/vcr/util/…
Показать ещё 10 комментариев
76

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

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

  • 1
    Я вижу, это действительно преимущество? Код выполняется для каждого примера независимо.
  • 2
    ИМО легче читать, а читаемость является огромным фактором для языков программирования.
Показать ещё 3 комментария
16

Я полностью заменил все применения переменных экземпляра в моих тестах rspec для использования let(). Я написал пример quickie для друга, который использовал его для обучения небольшому классу Rspec: http://ruby-lambda.blogspot.com/2011/02/agile-rspec-with-let.html

Как говорят некоторые другие ответы, пусть() оценивается лениво, поэтому загружает только те, которые требуют загрузки. Он сушит спецификацию и делает ее более читаемой. Фактически я портировал код Rspec let() для использования в моих контроллерах в стиле inherited_resource gem. http://ruby-lambda.blogspot.com/2010/06/stealing-let-from-rspec.html

Наряду с ленивой оценкой другое преимущество заключается в том, что в сочетании с ActiveSupport:: Concern и спецификацией/поддержкой/поведением "все-в-во-в-все" вы можете создать свой собственный spec-mini-DSL, специфичный для вашего приложения. Я написал для тестирования ресурсов Rack и RESTful.

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

  • 0
    Эй, Хо-Шен, я прочитал несколько твоих постов в блоге, прежде чем задавать этот вопрос. Что касается вашего # spec/friendship_spec.rb и # spec/comment_spec.rb Например, вы не думаете , что они делают его менее читаемым? Я понятия не имею, откуда приходят users и им нужно будет копать глубже.
  • 0
    Первые дюжины или около того людей, которым я показал формат, все находят, что он гораздо более читабелен, и некоторые из них начали писать с ним. Теперь у меня достаточно спецификационного кода с использованием let (), и я столкнулся с некоторыми из этих проблем. Я обнаруживаю, что иду к примеру и, начиная с самой внутренней группы примеров, снова работаю. Это тот же навык, что и использование высоко метапрограммируемой среды.
Показать ещё 5 комментариев
13

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

8

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

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

Может, это только я?

  • 9
    let всегда запоминает значение на время одного примера. Он не запоминает значение в нескольких примерах. before(:all) , напротив, позволяет повторно использовать инициализированную переменную в нескольких примерах.
  • 2
    если вы хотите использовать let (как сейчас кажется наилучшей практикой), но вам нужна конкретная переменная, которую нужно создать сразу же, вот что let! предназначен для. relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/...
5

пусть является функциональным, так как по сути это Proc. Также его кешируется.

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

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

Обновить

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

В этом случае мне просто нужно вызвать метод, на который отвечает объект. Итак, я призываю _.persisted? _ Метод на объекте как его правдивый. Все, что я пытаюсь сделать, это создать экземпляр объекта. Вы могли бы назвать пустым? или ноль? тоже. Дело не в тесте, а в том, чтобы оживить объект, вызвав его.

Таким образом, вы не можете рефакторинг

object.persisted?.should eq true

быть

object.should be_persisted 

как объект не был создан... его ленивый. :)

Обновление 2

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

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

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

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

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

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
  • 1
    +1 до (: все) ошибки потратили много дней нашего времени разработчиков.
1

"до" по умолчанию подразумевает before(:each). Ссылка на книгу Rspec, авторское право 2010, стр. 228.

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

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

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

И before, и let отлично подходят для сушки блоков "it".

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

0

Я использую 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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню