RSpec: Яка різниця між блоком let і a до?


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 або подібного інструменту.
апноедівінг

Відповіді:


146

Люди, здається, пояснили деякі основні способи їх відмінності, але пропустили 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_awesomeif, якщо 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 { 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 docs .
Джим Стюарт,

3

Схоже, ви використовуєте машиніста. Обережно, ви можете побачити деякі проблеми з make! усередині let (версія без вибуху), що відбувається поза глобальною транзакцією пристосування (якщо ви також використовуєте транзакційні прилади), таким чином псуючи дані для інших ваших тестів.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.