Когда использовать RSpec let ()? - PullRequest
437 голосов
/ 19 марта 2011

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

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

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

Ответы [ 9 ]

592 голосов
/ 19 марта 2011

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

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

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

79 голосов
/ 19 марта 2011

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

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

17 голосов
/ 19 марта 2011

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

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

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

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

13 голосов
/ 13 августа 2011

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

8 голосов
/ 03 апреля 2012

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

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

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

5 голосов
/ 07 мая 2013

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

Обновление

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

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

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

object.persisted?.should eq true

, чтобы быть

object.should be_persisted 

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

Обновление 2

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

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

subject(:object) {FactoryGirl.create :object}
2 голосов
/ 28 августа 2013

Примечание для Джозефа - если вы создаете объекты базы данных в 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 голос
/ 22 февраля 2013

«до» по умолчанию подразумевает before(:each).Ссылка Rspec Book, авторское право 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 голосов
/ 14 февраля 2018

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...