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

86

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

new2cpp
источник

Ответы:

191

Описание: Субъект 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указанный объект не будет упомянут в примере. Если вы хотите, чтобы ваш объект explict был создан без промедления (до запуска примера в его группе), скажите subject!вместо subject.

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

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

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

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

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

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

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

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
Вот Это Да! + 💯! «Фетиш единственного ожидания на пример», так сказать, на вес золота. Я никогда не чувствовал необходимости очень внимательно следовать этому (неправильному) правилу, но теперь я хорошо вооружен против людей, которые пытаются навязать его остальным из нас. Благодаря!
iconoclast
2
Кроме того, если вы хотите, чтобы ваши блоки с несколькими ожиданиями запускали все ожидаемые строки (вместо того, чтобы не запускаться в случае сбоя первой), вы можете использовать :aggregate_failuresтег в строке вроде it ​"marks a task complete"​, ​:aggregate_failures​ ​do(взято из книги Rails 5 Test Prescriptions)
лабиринт
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легче читать

Ren
источник
1

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

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

nikkypx
источник