В чем разница между предметом RSpec и let? Когда они должны использоваться или нет?

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

Ответы на вопрос(3)

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

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

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

Subject позволяет вам объявить объект тестирования, а затем повторно использовать его для любого количества последующих тестов. Это уменьшает повторение кода (высушивание кода)

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

Решение Вопроса

Предмет RSpec - это специальная переменная, которая относится к тестируемому объекту. Ожидания могут быть установлены на нем неявно, что поддерживает однострочные примеры. Это понятно читателю в некоторых идиоматических случаях, но в противном случае его трудно понять, и его следует избегать. RSpec-хlet переменные - это просто ленивые (записанные) переменные. За ними не так сложно следить, как за предметом, но они все же могут привести к запутанным тестам, поэтому их следует использовать с осторожностью.

ПредметКак это устроено

Предмет - это объект, который тестируется. RSpec имеет четкое представление о предмете. Это может или не может быть определено. Если это так, RSpec может вызывать методы, не обращаясь к нему явно.

По умолчанию, если первый аргумент для внешней группы примеров (describe или жеcontext block) является классом, 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.

Однако предмет определен,

Это ленивый экземпляр. То есть неявная реализация описанного класса или выполнение блока, переданногоsubject не бывает доsubject или названный предмет упоминается в примере. Если вы хотите, чтобы ваш предмет экспликации создавался с нетерпением (до запуска примера в его группе), скажитеsubject! вместоsubject.

Ожидания могут быть установлены на нем неявно (без записиsubject или имя названного субъекта):

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

Субъект существует для поддержки этого однострочного синтаксиса.

Когда его использовать

Неявныйsubject (вывод из группы примеров) трудно понять, потому что

Он создан за кулисами.Используется ли это неявно (вызываяis_expected без явного получателя) или явно (какsubject), он не дает читателю никакой информации о роли или природе объекта, по которому вызывается ожидание.Синтаксис примера с одной строкой не имеет описания примера (строковый аргументit в обычном синтаксисе примера), поэтому единственная информация, которую читатель имеет о цели примера, это само ожидание.

Следовательно,Полезно использовать неявный предмет только тогда, когда контекст, вероятно, будет хорошо понят всеми читателями, и в действительности нет необходимости в описании примера, Канонический случай - это проверка валидаций ActiveRecord с помощью совпадений musta:

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! variablle

Так что используйте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
 Andre Figueiredo21 апр. 2019 г., 20:30
Если вы хотите проверить только для однозначного числа (Int [0,9]), было бы лучше сделатьexpect(result.to_s).to match(/^[0-9]$/) - Я знаю, это уродливо, но это действительно проверяет, что вы говорите, или, возможно, использоватьbetween + is_a? IntegerНо здесь вы тоже тестируете тип. И простоlet... это не должно вызывать беспокойства, и на самом деле может быть лучше пересмотреть значения между примерами. В противном случае +1 за пост
 labyrinth04 сент. 2018 г., 21:56
Кроме того, если вы хотите, чтобы блоки с несколькими ожиданиями выполняли все ожидаемые строки (вместо того, чтобы не запускаться, если первый не прошел), вы можете использовать:aggregate_failures пометить в строке, какit ​"marks a task complete"​, ​:aggregate_failures​ ​do (взято из книги Rails 5 Test Prescription)
 Damon Aw10 мая 2019 г., 05:38
Мне нравится ваше объяснение об опасностяхlet! и не было, чтобы убедить моих товарищей по команде в одиночку. Я собираюсь отправить этот ответ.
 iconoclast10 июл. 2017 г., 16:18
Вот Это Да! + 💯! «Фетиш с единичным ожиданием» ценится на вес золота, так сказать. Я никогда не чувствовал необходимости очень строго следовать этому (неправильному) правилу, но теперь я хорошо вооружен против людей, которые пытаются навязать его остальным. Спасибо!
 Andre Figueiredo21 апр. 2019 г., 20:01
Я тоже против шумихи и фетишей, «одиночное ожидание на пример» в основном для тех, кто любит группировать много несвязанных ожиданий в одном примере. Семантически говоря, ваш пример не идеален, потому что он не проверяет однозначное число, он проверяет действительные числа в [0, 9] - что, к удивлению, может быть закодировано в гораздо более читаемом единственном ожиданииexpect(result).to be_between(0, 9).

Ваш ответ на вопрос