Яка різниця між темою RSpec і нехай? Коли їх слід використовувати чи ні?


85

http://betterspecs.org/#subject містить деяку інформацію про subjectта let. Однак я досі незрозумілий щодо різниці між ними. Крім того, публікація SO Що є аргументом проти використання раніше, нехай і предмета в тестах RSpec? сказав, що краще не використовувати ні, subjectні let. Куди мені піти? Я така розгублена.

Відповіді:


187

Короткий зміст: Тема RSpec - це спеціальна змінна, яка відноситься до об'єкта, що тестується. На нього можна покласти сподівання неявно, що підтримує однорядкові приклади. Це зрозуміло для читача в деяких ідіоматичних випадках, але в іншому випадку важко зрозуміти, і його слід уникати. letЗмінні RSpec - це просто ліниво створені (запам'ятовувані) змінні. Вони не такі важкі для вивчення, як предмет, але все одно можуть призвести до заплутаних тестів, тому їх слід використовувати з розсудом.

Тема

Як це працює

Суб'єкт - це об'єкт, що перевіряється. RSpec має чітке уявлення про предмет. Це може бути визначено або не визначено. Якщо це так, RSpec може викликати на ньому методи, не посилаючись на нього явно.

За замовчуванням, якщо першим аргументом найвіддаленішої групи прикладів ( describeабо contextблоків) є клас, RSpec створює екземпляр цього класу і призначає його темі. Наприклад, такі пропуски:

class A
end

describe A do
  it "is instantiated by RSpec" do
    expect(subject).to be_an(A)
  end
end

Ви можете визначити предмет самостійно за допомогою subject:

describe "anonymous subject" do
  subject { A.new }
  it "has been instantiated" do
    expect(subject).to be_an(A)
  end
end

Ви можете дати темі ім’я, коли його визначаєте:

describe "named subject" do
  subject(:a) { A.new }
  it "has been instantiated" do
    expect(a).to be_an(A)
  end
end

Навіть якщо ви називаєте тему, ви все одно можете посилатися на неї анонімно:

describe "named subject" do
  subject(:a) { A.new }
  it "has been instantiated" do
    expect(subject).to be_an(A)
  end
end

Ви можете визначити більше одного іменованого предмета. Останній визначений названий суб’єкт - анонімний subject.

Однак тема визначена,

  1. Це ліниво. Тобто, неявна інстанція описуваного класу або виконання блоку, якому передано subject, не відбувається, поки subjectабо не буде вказано названий предмет у прикладі. Якщо ви хочете, щоб ваш експлікт-предмет був інтенсивно створений (до запуску прикладу в його групі), скажіть subject!замість subject.

  2. На нього можна неявно покласти сподівання (без написання subjectабо імені названого суб’єкта):

    describe A do
      it { is_expected.to be_an(A) }
    end
    

    Тема існує для підтримки цього однорядкового синтаксису.

Коли його використовувати

Імпліцитний subject(що випливає з групи прикладів) важко зрозуміти, оскільки

  • Це зроблено за кулісами.
  • Незалежно від того, використовується воно неявно (за допомогою виклику is_expectedбез явного приймача) або явно (як subject), воно не дає читачеві ніякої інформації про роль або природу об’єкта, на який викликається очікування.
  • Приклад синтаксису з одним вкладишем не має прикладу опису (аргумент рядка для itзвичайного синтаксису прикладу), тому єдиною інформацією, яку читач має про мету прикладу, є саме очікування.

Тому корисно використовувати неявну тему лише тоді, коли контекст, ймовірно, добре розуміється всіма читачами, і насправді немає потреби в прикладі опису . Канонічний випадок - це тестування перевірок ActiveRecord за допомогою збігів, що відповідають за вимоги:

describe Article do
  it { is_expected.to validate_presence_of(:title) }
end

Експлікт анонімний subject(визначений subjectбез імені) трохи кращий, тому що читач може бачити, як він створений, але

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

Іменований предмет надає ім’я, що виявляє намір, але єдина причина використовувати названий предмет замість letзмінної - це те, що ви хочете використовувати анонімний предмет час від часу, і ми просто пояснили, чому анонімний суб’єкт важко зрозуміти.

Отже, законне використання явного анонімного subjectабо названого суб’єкта дуже рідкісне .

let змінні

Як вони працюють

let змінні так само, як названі предмети, за винятком двох відмінностей:

  • вони визначаються за допомогою let/ let!замість subject/subject!
  • вони не встановлюють анонімних subjectчи не дозволяють неявно викликати до нього сподівання.

Коли їх використовувати

Це цілком законно використовувати letдля зменшення дублювання серед прикладів. Однак робіть це лише тоді, коли це не жертвує ясністю тесту. Найбезпечніший час використання let- це коли letзмінна повністю зрозуміла з її назви (щоб читачеві не потрібно було знаходити визначення, яке може бути на відстані рядків, щоб зрозуміти кожен приклад), і воно використовується так само у кожному прикладі. Якщо будь-яка з цих речей не відповідає дійсності, спробуйте визначити об’єкт у звичайній старій локальній змінній або викликати заводський метод прямо в прикладі.

let!ризиковано, бо це не лінь. Якщо хтось додає приклад до групи прикладів, що містить let!, але приклад не потребує let!змінної,

  • цей приклад буде важко зрозуміти, оскільки читач побачить let!змінну і запитає, чи впливає і як це впливає на приклад
  • приклад буде повільнішим, ніж потрібно, через час, let!затрачений на створення змінної

Тож використовуйте let!, якщо взагалі, лише невеликі, прості групи прикладів, де менш імовірно, що майбутні автори прикладів потраплять у цю пастку.

Фетиш з одним очікуванням на приклад

Існує загальне надмірне використання предметів або letзмінних, які варто обговорити окремо. Деякі люди люблять використовувати їх так:

describe 'Calculator' do
  describe '#calculate' do
    subject { Calculator.calculate }
    it { is_expected.to be >= 0 }
    it { is_expected.to be <= 9 }
  end
end

(Це простий приклад методу, який повертає число, для якого нам потрібні два очікування, але цей стиль може мати набагато більше прикладів / очікувань, якщо метод повертає більш складне значення, яке потребує багатьох очікувань та / або має багато побічних ефектів, які всім потрібні очікування.)

Люди роблять це тому, що чули, що на приклад потрібно мати лише одне очікування (яке змішується з діючим правилом, згідно з яким потрібно протестувати лише один виклик методу на приклад), або тому, що вони закохані в хитрість RSpec. Не робіть цього, будь то анонімний або названий предмет чи letзмінна! Цей стиль має кілька проблем:

  • Анонімний суб’єкт не є предметом прикладів - метод є предметом. Написання тесту таким чином псує мову, ускладнюючи роздуми.
  • Як завжди з однорядковими прикладами, немає місця для пояснення значення очікувань.
  • Тема повинна бути побудована для кожного прикладу, що є повільним.

Натомість напишіть один приклад:

describe 'Calculator' do
  describe '#calculate' do
    it "returns a single-digit number" do
      result = Calculator.calculate
      expect(result).to be >= 0
      expect(result).to be <= 9
    end
  end
end

17
Оце Так! + 💯! "Фетиш з одним очікуванням на приклад" на вагу золота, так би мовити. Я ніколи не відчував потреби дотримуватися цього (помилкового) правила дуже уважно, але зараз я добре озброєний проти людей, які намагаються нав'язати це решті нам. Дякую!
іконоборці

2
Крім того, якщо ви хочете, щоб ваші блоки з багатьма очікуваннями запускали всі очікувані рядки (замість того, щоб не запускатись, якщо перший не вдається), ви можете використовувати :aggregate_failuresтег у такому рядку, як it ​"marks a task complete"​, ​:aggregate_failures​ ​do(взято з книги Rails 5 Test Prescripts)
labyrinth

1
Я теж проти ажіотажу та фетишу, "єдине очікування на приклад" в основному для тих, хто любить групувати багато не пов'язаних між собою очікувань в одному прикладі. Семантично кажучи, ваш приклад не є ідеальним, оскільки він не перевіряє одноцифрове число, він перевіряє реальні числа в [0, 9], - що, як несподівано здивує, може бути закодовано в набагато більш читабельному єдиному очікуванні expect(result).to be_between(0, 9).
Андре Фігейредо,

Якщо ви хочете перевірити лише одноцифрове число (Int [0,9]), це було б доцільніше зробити expect(result.to_s).to match(/^[0-9]$/)- я знаю, що це потворно, але це справді перевірка того, що ви говорите, або, можливо, використання between+ is_a? Integer, але тут ви тестуєте тип теж. І просто let.. це не повинно викликати занепокоєння, і насправді може бути краще переоцінити значення між прикладами. В іншому випадку +1 за посаду
Андре Фігейредо

Мені подобається ваше пояснення щодо небезпеки, let!і я не хотів переконувати своїх товаришів по команді самостійно. Я збираюся надіслати цю відповідь.
Деймон Ау

5

Subjectі letє лише інструментами, які допоможуть привести вас в порядок і пришвидшити тестування. Люди в спільноті rspec їх використовують, тому я б не хвилювався, чи можна їх використовувати чи ні. Вони можуть використовуватися подібним чином, але служать дещо іншим цілям

Subjectдозволяє заявити про тестуваного, а потім повторно використовувати його для будь-якої кількості наступних тестових випадків після цього. Це зменшує повторення коду (ВИСУШУЄ ваш код)

Letє альтернативою before: eachблокам, які призначають тестові дані змінним екземпляра. Letдає вам пару переваг. Спочатку він кешує значення, не призначаючи його змінній екземпляра. По-друге, він ліниво оцінюється, а це означає, що він не оцінюється, поки специфікація не вимагає цього. Це letдопомагає вам пришвидшити тестування. Я також думаю, що letйого легше читати


1

subjectце те, що тестується, як правило, екземпляр або клас. letпризначений для призначення змінних у ваших тестах, які оцінюються ліниво порівняно із використанням змінних екземпляра. У цій темі є кілька приємних прикладів.

https://github.com/reachlocal/rspec-style-guide/issues/6

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