RSpec: В чем разница между let и перед блоком?

90

В чем разница между letи beforeблоком в RSpec?

И когда использовать каждый?

Какой будет хороший подход (пусть или раньше) в приведенном ниже примере?

let(:user) { User.make !}
let(:account) {user.account.make!}

before(:each) do
 @user = User.make!
 @account = @user.account.make!
end

Я изучил этот пост stackoverflow

Но хорошо ли определять let для ассоциаций, как указано выше?

крийсна
источник
1
В основном, let используется людьми, которым не нравятся переменные экземпляра. В качестве побочного примечания вам следует подумать об использовании FactoryGirl или аналогичного инструмента.
apneadiving

Ответы:

147

Люди, кажется, объяснили некоторые из основных способов, которыми они отличаются, но упустили из виду before(:all)и не объясняют, почему именно их следует использовать.

Я считаю, что переменным экземпляра нет места в подавляющем большинстве спецификаций, отчасти из-за причин, упомянутых в этом ответе , поэтому я не буду упоминать их здесь как вариант.

пусть блоки

Код внутри letблока выполняется только при ссылке, ленивая загрузка означает, что порядок этих блоков не имеет значения. Это дает вам много энергии, чтобы сократить повторяющиеся настройки в соответствии с вашими спецификациями.

Один (очень маленький и надуманный) пример этого:

let(:person)     { build(:person) }
subject(:result) { Library.calculate_awesome(person, has_moustache) }

context 'with a moustache' do
  let(:has_moustache) { true } 
  its(:awesome?)      { should be_true }
end

context 'without a moustache' do
  let(:has_moustache) { false } 
  its(:awesome?)      { should be_false }
end

Вы можете видеть, что has_moustacheв каждом случае это определяется по-разному, но нет необходимости повторять subjectопределение. Важно отметить, что будет использоваться последний letблок, определенный в текущем контексте. Это хорошо для установки значения по умолчанию, которое будет использоваться для большинства спецификаций, которые при необходимости могут быть перезаписаны.

Например, проверка возвращаемого значения, calculate_awesomeесли передана personмодель со top_hatзначением true, но без усов, будет:

context 'without a moustache but with a top hat' do
  let(:has_moustache) { false } 
  let(:person)        { build(:person, top_hat: true) }
  its(:awesome?)      { should be_true }
end

Еще одно замечание о блоках let: их не следует использовать, если вы ищете что-то, что было сохранено в базе данных (т.е. Library.find_awesome_people(search_criteria)), поскольку они не будут сохранены в базе данных, если на них уже не было ссылки. let!или beforeблоки - вот что следует использовать здесь.

Кроме того, никогда не используйте beforeдля запуска выполнения letблоков, это то, let!для чего!

позволять! блоки

let!блоки выполняются в том порядке, в котором они определены (во многом как перед блоком). Основное отличие от блоков before заключается в том, что вы получаете явную ссылку на эту переменную, вместо того, чтобы возвращаться к переменным экземпляра.

Как и в случае с letблоками, если несколько let!блоков определены с одним и тем же именем, при выполнении будет использоваться самый последний. Основное различие состоит в том, что при таком использовании let!блоки будут выполняться несколько раз, тогда как letблок будет выполняться только в последний раз.

перед (: каждый) блоками

before(:each)по умолчанию используется перед блоком, и поэтому на него можно ссылаться, before {}а не указывать полный before(:each) {}каждый раз.

Я лично предпочитаю использовать beforeблоки в нескольких основных ситуациях. Я буду использовать блоки before, если:

  • Я использую насмешку, заглушку или дубли
  • Имеется установка любого разумного размера (обычно это признак того, что ваши заводские характеристики настроены неправильно)
  • Есть ряд переменных, на которые мне не нужно ссылаться напрямую, но они необходимы для настройки.
  • Я пишу тесты функциональных контроллеров в рельсах, и я хочу выполнить конкретный запрос на тестирование (т.е. before { get :index }). Хотя вы можете использовать subjectэто во многих случаях, иногда это кажется более явным, если вам не нужна ссылка.

Если вы обнаружите, что пишете большие beforeблоки для своих спецификаций, проверьте свои фабрики и убедитесь, что вы полностью понимаете особенности и их гибкость.

перед (: все) блоки

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

Один из примеров (который вообще вряд ли повлияет на время выполнения) - это имитация переменной ENV для теста, которую вам нужно сделать только один раз.

Надеюсь, это поможет :)

Джей
источник
Для пользователей minitest, которыми я являюсь, но я не заметил тега RSpec в этом разделе вопросов и ответов, эта before(:all)опция не существует в Minitest. Вот некоторые обходные пути в комментариях: github.com/seattlerb/minitest/issues/61
MrYoshiji
Ссылка на статью
Дилан Пирс
Спасибо @DylanPierce. Я не могу найти никаких копий этой статьи, поэтому я сослался на ответ SO, который вместо этого обращается к этому :)
Джей,
Спасибо :) очень признателен
Дилан Пирс
1
itsбольше не входит в состав rspec-core. Более современный идиоматический способ it { is_expected.to be_awesome }.
Зубин
26

Я предпочитаю почти всегда let. Сообщение, на которое вы ссылаетесь, указывает, что letэто также быстрее. Однако иногда, когда нужно выполнить много команд, я мог бы использовать, before(:each)потому что его синтаксис более ясен, когда задействовано много команд.

В вашем примере я определенно предпочел бы использовать letвместо before(:each). Вообще говоря, когда выполняется лишь некоторая инициализация переменной, я предпочитаю использовать let.

Спирос
источник
15

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

микевебер
источник
20
Вы можете использовать let!для определения методов, вызываемых перед каждым примером. См. Документацию RSpec .
Джим Стюарт
3

Похоже, вы используете Машинист. Остерегайтесь, вы можете увидеть некоторые проблемы с make! внутри let (версия без взрыва), происходящая вне глобальной транзакции фикстуры (если вы также используете транзакционные фикстуры), таким образом искажая данные для других ваших тестов.

Тим Коннор
источник