Во второй статье этой короткой серии вы узнаете, как использовать различные средства сравнения, которые поставляются с RSpec. В нем также показано, как разделить ваш набор тестов с помощью тегов, как работают обратные вызовы и как извлечь некоторые данные. Мы немного расширим базовый набор для выживания из первой статьи и покажем вам, что достаточно опасно, если не повесить слишком много веревки.
темы
- Matchers
- Позволять
- Предметы
- Callbacks
- Генераторы
- Теги
В первой статье мы потратили немало времени, пытаясь ответить на вопрос «почему?» Тестирования. Я предлагаю, чтобы мы вернулись к «как?» И избавились от дальнейшего контекста. Мы уже подробно рассмотрели эту часть. Давайте посмотрим, что еще может предложить RSpec, с которым вы как новичок можете справиться сразу.
Matchers
Так что это приблизится к сути вещей. RSpec предоставляет вам массу так называемых совпадений. Это ваш хлеб с маслом, когда вы пишете свои ожидания. До сих пор вы видели .to eq
и .not_to eq
. Но есть гораздо больший арсенал, чтобы написать свои спецификации. Вы можете проверить, чтобы вызвать ошибки, для истинных и ложных значений или даже для определенных классов. Давайте запустим несколько вариантов, чтобы вы начали:
-
.to eq
-
.not_to eq
Это тесты на эквивалентность.
Некоторые спецификации
1
2
3
4
5
6
7
8
|
…
it ‘some clever description’ do
expect(agent.enemy).to eq ‘Ernst Stavro Blofeld’
expect(agent.enemy).not_to eq ‘Winnie Pooh’
end
…
|
Внимание!
Короче говоря, я упаковал два ожидаемых оператора в один блок it
. Тем не менее, рекомендуется проверять только одну вещь за тест. Это делает вещи более сфокусированными, и ваши тесты станут менее хрупкими, когда вы что-то измените.
-
.to be_truthy
-
.to be true
Некоторые спецификации
1
2
3
4
5
6
7
8
|
…
it ‘some clever description’ do
expect(agent.hero?).to be_truthy
expect(enemy.megalomaniac?).to be true
end
…
|
Разница в том, что be_truthy
имеет значение true, когда оно не равно nil
или false
. Таким образом, это пройдет, если результат не является ни одним из этих двух — своего рода «истинно похожим». с другой стороны, чтобы .to be true
принимает только значение, которое является true
и ничего больше.
-
.to be_falsy
-
.to be false
Некоторые спецификации
1
2
3
4
5
6
7
8
|
…
it ‘some clever description’ do
expect(agent.coward?).to be_falsy
expect(enemy.megalomaniac?).to be false
end
…
|
Как и в двух приведенных выше примерах, .to be_falsy
ожидает либо значение false
либо значение nil
, а .to be false
будет выполнять только прямое сравнение с false
.
-
.to be_nil
-
.to_not be_nil
И последнее по порядку, но не по значению, это тестирование именно на nil
. Я избавляю вас от примера.
-
.to match()
Я надеюсь, что вы уже имели удовольствие просматривать регулярные выражения. Если нет, то это последовательность символов, с помощью которой вы можете определить шаблон, который вы помещаете между двумя косыми чертами для поиска строк. Регулярное выражение может быть очень удобно, если вы хотите искать более широкие шаблоны, которые вы можете обобщить в таком выражении.
Некоторые спецификации
1
2
3
4
5
6
7
|
…
it ‘some clever description’ do
expect(agent.number.to_i).to match(/\d{3}/)
end
…
|
Предположим, мы имеем дело с такими агентами, как Джеймс Бонд, 007, которым назначены трехзначные номера. Тогда мы могли бы проверить это таким образом — примитивно здесь, конечно.
-
>
-
<
-
<=
-
>=
Сравнения пригодятся чаще, чем можно подумать. Я предполагаю, что приведенные ниже примеры будут охватывать то, что вам нужно знать.
Некоторые спецификации
01
02
03
04
05
06
07
08
09
10
11
12
13
|
…
it ‘some clever description’ do
…
expect(agent.number).to be < quartermaster.number
expect(agent.number).to be > m.number
expect(agent.kill_count).to be >= 25
expect(quartermaster.number_of_gadgets).to be <= 5
end
…
|
Теперь мы становимся куда менее скучными. Вы также можете проверить для классов и типов:
-
.to be_an_instance_of
-
.to be_a
-
.to be_an
Некоторые спецификации
01
02
03
04
05
06
07
08
09
10
11
12
|
…
it ‘some clever description’ do
mission = Mission.create(name: ‘Moonraker’)
agent = Agent.create(name: ‘James Bond’)
mission.agents << agent
expect(@mission.agents).not_to be_an_instance_of(Agent)
expect(@mission.agents).to be_a(ActiveRecord::Associations::CollectionProxy)
end
…
|
В приведенном выше фиктивном примере вы можете видеть, что список агентов, связанных с миссией, относится не к классу Agent
а к ActiveRecord::Associations::CollectionProxy
. От этого следует отказаться, так как мы можем легко проверить сами классы, оставаясь при этом очень выразительными. .to be_a
и .to be_an
делают одно и то же. У вас есть оба варианта для удобства чтения.
Тестирование на ошибки также чрезвычайно удобно в RSpec. Если вы новичок в Rails и еще не уверены, какие ошибки может вызвать у вас фреймворк, вы можете не чувствовать необходимости их использовать — конечно, это имеет смысл. Однако на более позднем этапе развития вы найдете их очень удобными. У вас есть четыре способа борьбы с ними:
-
.to raise_error
Это самый общий способ. Какая бы ошибка ни возникала, она будет брошена в вашу сеть.
-
.to raise_error(ErrorClass)
Таким образом, вы можете точно указать, из какого класса должна исходить ошибка.
-
.to raise_error(ErrorClass, "Some error message")
Это еще более детально, поскольку вы упоминаете не только класс ошибки, но и конкретное сообщение, которое должно быть выдано с ошибкой.
-
.to raise_error("Some error message)
Или вы просто упоминаете само сообщение об ошибке без класса ошибки. Ожидаемая часть должна быть написана немного по-другому — нам нужно обернуть часть под текстом в самом блоке кода:
Некоторые спецификации
01
02
03
04
05
06
07
08
09
10
11
12
|
…
it ‘some clever description’ do
agent = Agent.create(name: ‘James Bond’)
expect{agent.lady_killer?}.to raise_error(NoMethodError)
expect{double_agent.name}.to raise_error(NameError)
expect{double_agent.name}.to raise_error(«Error: No double agents around»)
expect{double_agent.name}.to raise_error(NameError, «Error: No double agents around»)
end
…
|
-
.to start_with
-
.to end_with
Поскольку мы часто имеем дело с коллекциями при создании веб-приложений, было бы неплохо иметь в них инструмент для просмотра. Здесь мы добавили двух агентов, Q и Джеймса Бонда, и просто хотели узнать, кто придет первым и последним в наборе агентов для конкретной миссии — здесь Moonraker.
Некоторый агент Спец
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
…
it ‘some clever description’ do
moonraker = Mission.create(name: ‘Moonraker’)
bond = Agent.create(name: ‘James Bond’)
q = Agent.create(name: ‘Q’)
moonraker.agents << bond
moonraker.agents << q
expect(moonraker.agents).to start_with(bond)
expect(moonraker.agents).to end_with(q)
end
..
|
-
.to include
Этот также полезен для проверки содержимого коллекций.
Некоторый агент Спец
01
02
03
04
05
06
07
08
09
10
11
|
…
it ‘some clever description’ do
mission = Mission.create(name: ‘Moonraker’)
bond = Agent.create(name: ‘James Bond’)
mission.agents << bond
expect(mission.agents).to include(bond)
end
…
|
- предикаты
Эти сопоставители предикатов являются функцией RSpec для динамического создания сопоставлений для вас. Если у вас есть методы предикатов в ваших моделях, например (заканчивающиеся знаком вопроса), тогда RSpec знает, что он должен создавать для вас соответствия, которые вы можете использовать в своих тестах. В приведенном ниже примере мы хотим проверить, является ли агент Джеймсом Бондом:
Модель агента
1
2
3
4
5
6
7
8
9
|
class Agent < ActiveRecord::Base
def bond?
name == ‘James Bond’ && number == ‘007’ && gambler == true
end
…
end
|
Теперь мы можем использовать это в наших спецификациях следующим образом:
Некоторый агент Спец
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
…
it ‘some clever description’ do
agent = Agent.create(name: ‘James Bond’, number: ‘007’, gambler: true)
expect(agent).to be_bond
end
it ‘some clever description’ do
agent = Agent.create(name: ‘James Bond’)
expect(agent).not_to be_bond
end
…
|
RSpec позволяет нам использовать имя метода без знака вопроса — для формирования лучшего синтаксиса, я полагаю. Круто, не правда ли?
Позволять
let
и let!
на первый взгляд может выглядеть как переменная, но на самом деле это вспомогательные методы. Первый лениво оценивается, что означает, что он запускается и оценивается только тогда, когда спецификация фактически использует его, а второй выполняется с ударом (!) Независимо от того, используется он спецификацией или нет. Обе версии запоминаются, и их значения будут кэшироваться в одной и той же области видимости.
Некоторый Спек Файл
1
2
3
4
5
6
7
8
9
|
describe Mission, ‘#prepare’, :let do
let(:mission) { Mission.create(name: ‘Moonraker’) }
let!(:bond) { Agent.create(name: ‘James Bond’) }
it ‘adds agents to a mission’ do
mission.prepare(bond)
expect(mission.agents).to include bond
end
end
|
Вариант взрыва, который не лениво оценивается, может занять много времени и, следовательно, дорого, если он станет вашим модным новым другом. Почему? Потому что он настроит эти данные для каждого рассматриваемого теста, несмотря ни на что, и может в конечном итоге оказаться одной из этих неприятных вещей, которые значительно замедляют ваш набор тестов.
Вы должны знать эту особенность RSpec, так как let
широко известен и используется. При этом, следующая статья покажет вам некоторые проблемы, о которых вы должны знать. Используйте эти вспомогательные методы с осторожностью, или, по крайней мере, в небольших дозах на данный момент.
Предметы
RSpec предлагает вам возможность объявить объект тестирования очень явно. Есть лучшие решения для этого, и мы обсудим недостатки этого подхода в следующей статье, когда я покажу несколько вещей, которых вы обычно хотите избежать. Но сейчас давайте посмотрим, что может сделать для вас предмет:
Некоторый Спек Файл
1
2
3
4
5
6
7
|
describe Agent, ‘#status’ do
subject { Agent.create(name: ‘Bond’) }
it ‘returns the agents status’ do
expect(subject.status).not_to be ‘MIA’
end
end
|
Этот подход может, с одной стороны, помочь вам уменьшить дублирование кода, объявив главного героя один раз в определенной области, но он также может привести к тому, что называется тайным гостем. Это просто означает, что мы можем оказаться в ситуации, когда мы используем данные для одного из наших тестовых сценариев, но больше не имеем понятия, откуда они на самом деле и из чего они состоят. Подробнее об этом в следующей статье.
Callbacks
Если вы еще не знаете об обратных вызовах, позвольте мне кратко рассказать вам. Обратные вызовы выполняются в определенных точках жизненного цикла кода. В терминах Rails это будет означать, что у вас есть код, который запускается до того, как объекты будут созданы, обновлены, уничтожены и т. Д.
В контексте RSpec это жизненный цикл запускаемых тестов. Это просто означает, что вы можете указать хуки, которые должны запускаться до или после каждого теста, например, в файле спецификации, или просто вокруг каждого теста. Есть несколько более детализированных вариантов, но я рекомендую, чтобы мы пока не терялись в деталях. Перво-наперво:
-
before(:each)
Этот обратный вызов запускается перед каждым тестовым примером.
Некоторый Спек Файл
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
describe Agent, ‘#favorite_gadget’ do
before(:each) do
@gagdet = Gadget.create(name: ‘Walther PPK’)
end
it ‘returns one item, the favorite gadget of the agent ‘ do
agent = Agent.create(name: ‘James Bond’)
agent.favorite_gadgets << @gadget
expect(agent.favorite_gadget).to eq ‘Walther PPK’
end
…
end
|
Допустим, вам понадобится определенный гаджет для каждого теста, который вы запускаете в определенной области. before
позволяет вам извлечь это в блок и подготовить этот небольшой фрагмент для вас удобно. Когда вы настраиваете данные таким образом, вы, конечно, должны использовать переменные экземпляра, чтобы иметь доступ к ним среди различных областей.
Внимание!
Не обманывайтесь удобством в этом примере. То, что вы можете делать такие вещи, не означает, что вы должны это делать. Я хочу не заходить на территорию AntiPattern и не выводить вас из себя, но, с другой стороны, я хочу немного объяснить и недостатки этих простых манекенов.
В приведенном выше примере было бы гораздо более выразительным, если бы вы устанавливали необходимые объекты для каждого теста. Особенно в больших файлах спецификаций вы можете быстро потерять из виду эти маленькие соединения и затруднить для других собирать вместе эти головоломки.
-
before(:all)
Этот блок before
выполняется только один раз перед всеми остальными примерами в файле спецификации.
Некоторый Спек Файл
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
describe Agent, ‘#enemy’ do
before(:all) do
@main_villain = Villain.create(name: ‘Ernst Stavro Blofeld’)
@mission = Mission.create(name: ‘Moonraker’)
@mission.villains << @main_villain
end
it ‘returns the main enemy Bond has to face in his mission’ do
agent = Agent.create(name: ‘James Bond’)
@mission.agents << agent
expect(agent.enemy).to eq ‘Ernst Stavro Blofeld’
end
…
end
|
Когда вы помните четыре фазы тестирования, before
блокировками иногда полезно настроить что-то для вас, что нужно регулярно повторять — возможно, вещи, которые имеют немного больше мета-характера.
after(:each)
и after(:all)
ведут себя одинаково, но просто запускаются после выполнения ваших тестов. after
часто используется для очистки ваших файлов. Но я думаю, что пока рано говорить об этом. Так что запишите это в память, знайте, что оно есть, если оно вам понадобится, и давайте перейдем к изучению других, более простых вещей.
Все эти обратные вызовы могут быть размещены стратегически в соответствии с вашими потребностями. Поместите их в любую область describe
блока, которая вам нужна для их запуска — их не обязательно размещать поверх вашего спецификации. Они могут быть легко вложены в ваши спецификации.
Некоторый Спек Файл
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
describe Agent do
before(:each) do
@mission = Mission.create(name: ‘Moonraker’)
@bond = Agent.create(name: ‘James Bond’, number: ‘007’)
end
describe ‘#enemy’ do
before(:each) do
@main_villain = Villain.create(name: ‘Ernst Stavro Blofeld’)
@mission.villains << @main_villain
end
describe ‘Double 0 Agent with associated mission’ do
it ‘returns the main enemy the agent has to face in his mission’ do
@mission.agents << @bond
expect(@bond.enemy).to eq ‘Ernst Stavro Blofeld’
end
end
describe ‘Low-level agent with associated mission’ do
it ‘returns no info about the main villain involved’ do
some_schmuck = Agent.create(name: ‘Some schmuck’, number: ‘1024’)
@mission.agents << some_schmuck
expect(some_schmuck.enemy).to eq ‘That’s above your paygrade!’
end
end
…
…
…
end
end
|
Как вы можете заметить, вы можете разместить блоки обратного вызова в любой области по своему вкусу и идти так глубоко, как вам нужно. Код в обратном вызове будет выполнен в рамках любой области описания блока. Но небольшой совет: если вы чувствуете необходимость вложения слишком много, а вещи кажутся немного запутанными и сложными, пересмотрите свой подход и подумайте, как можно упростить тесты и их настройку. ПОЦЕЛУЙ! Держать его просто глупо. Кроме того, обратите внимание на то, как хорошо это читается, когда мы заставляем эти тесты провалиться
Выход
01
02
03
04
05
06
07
08
09
10
11
12
13
|
Failures:
1) Agent#enemy Double 0 Agent with associated mission returns the main enemy the agent has to face in his mission
Failure/Error: expect(@bond.enemy).to eq ‘Ernst Stavro Blofeld’
expected: «Ernst Stavro Blofeld»
got: «Blofeld»
2) Agent#enemy Low-level agent with associated mission returns no info about the main villain involved
Failure/Error: expect(some_schmuck.enemy).to eq ‘That’s above your paygrade!’
expected: «That’s above your paygrade!»
got: «Blofeld»
|
Генераторы
Давайте также кратко рассмотрим, какие генераторы предоставляет RSpec для вас. Вы уже видели один, когда мы использовали rails generate rspec:install
. Этот маленький парень сделал настройку RSpec для нас быстрой и легкой. Что еще у нас есть?
-
rspec:model
Хотите иметь еще одну фиктивную модель?
Терминал
1
|
rails generate rspec:model another_dummy_model
|
Выход
1
|
create spec/models/another_dummy_model_spec.rb
|
Быстро, не так ли? Или новую спецификацию для проверки контроллера, например:
-
rspec:controller
Терминал
1
|
rails generate rspec:controller dummy_controller
|
Выход
1
|
spec/controllers/dummy_controller_controller_spec.rb
|
-
rspec:view
То же самое работает для взглядов, конечно. Мы не будем тестировать подобные представления. Спецификации для представлений дают вам наименьшую отдачу, а в почти любом сценарии вполне достаточно косвенно проверить ваши представления с помощью функциональных тестов.
Функциональные тесты не являются специальностью RSpec как таковые и больше подходят для другой статьи. При этом, если вам любопытно, посмотрите Капибару , которая является отличным инструментом для такого рода вещей. Он позволяет вам тестировать целые потоки, которые объединяют несколько частей вашего приложения — тестирование полных функций при моделировании работы браузера. Например, пользователь, который оплачивает несколько товаров в корзине.
-
rspec:helper
Та же самая стратегия генераторов позволяет нам также разместить помощника без особых хлопот.
Терминал
1
|
rails generate rspec:helper dummy_helper
|
Выход
1
|
create spec/helpers/dummy_helper_helper_spec.rb
|
Двойная часть helper_helper
не была случайностью. Когда мы дадим ему более «значимое» имя, вы увидите, что RSpec просто присоединяет _helper
самостоятельно.
Терминал
1
|
rails generate rspec:helper important_stuff
|
Выход
1
|
create spec/helpers/important_stuff_helper_spec.rb
|
Слово о помощниках
Нет, этот каталог не место для хранения ваших драгоценных вспомогательных методов, которые появляются при рефакторинге ваших тестов. На самом деле они будут идти под spec/support
. spec/helpers
предназначен для тестов, которые вы должны написать для своих помощников вида — set_date
может служить такой помощник, как set_date
. Да, полное тестовое покрытие вашего кода должно также включать эти вспомогательные методы. Тот факт, что они часто кажутся небольшими и тривиальными, не означает, что мы должны игнорировать их или игнорировать их потенциальные ошибки, которые мы хотим поймать. Чем сложнее на самом деле получается помощник, тем больше у вас причин для этого писать helper_spec
!
На случай, если вы начнете играть с ним сразу, имейте в виду, что вам нужно запускать ваши вспомогательные методы на helper
объекте, когда вы пишете свои вспомогательные тесты, чтобы работать. Таким образом, они могут быть выставлены только с помощью этого объекта. Что-то вроде этого:
Some Helper Spec
01
02
03
04
05
06
07
08
09
10
11
|
describe ‘#set_date’ do
…
helper.set_date
…
end
…
|
Вы можете использовать генераторы того же типа для спецификаций функций, спецификаций интеграции и спецификаций почтовых программ. На сегодняшний день это выходит за рамки наших возможностей, но вы можете зафиксировать их в памяти для будущего использования:
- RSpec: почтовик
- RSpec: функция
- RSpec: интеграция
Взгляд на сгенерированные спецификации
Спецификации, которые мы создали с помощью генератора выше, готовы к работе, и вы можете сразу же добавить туда свои тесты. Давайте немного посмотрим на разницу между спецификациями:
спецификации / модель / dummy_model_spec.rb
1
2
3
4
5
|
require ‘rails_helper’
RSpec.describe DummyModel, type: :model do
pending «add some examples to (or delete) #{__FILE__}»
end
|
спецификации / контроллеры / dummy_controller_controller_spec.rb
1
2
3
4
|
require ‘rails_helper’
RSpec.describe DummyControllerController, type: :controller do
end
|
спецификации / хелперы / dummy_helper_helper_spec.rb
1
2
3
4
5
|
require ‘rails_helper’
RSpec.describe DummyHelperHelper, type: :helper do
pending «add some examples to (or delete) #{__FILE__}»
end
|
Не нужно вундеркинду, чтобы понять, что у них всех разные типы. Это :type
метаданных RSpec дает вам возможность разрезать и нарезать кубики ваших тестов по файловым структурам. Таким образом, вы можете лучше ориентироваться на эти тесты. Скажем, вы хотите, чтобы какие-то помощники загружались, например, только для спецификаций контроллера. Другим примером может быть то, что вы хотите использовать другую структуру каталогов для спецификаций, которые RSpec не ожидает. Наличие этих метаданных в ваших тестах позволяет продолжать использовать функции поддержки RSpec, а не отключать набор тестов. Таким образом, вы можете использовать любую структуру каталогов, которая вам подходит, если вы добавите метаданные этого :type
.
С другой стороны, ваши стандартные тесты RSpec не зависят от этих метаданных. Когда вы используете эти генераторы, они будут добавлены бесплатно, но вы также можете полностью их избежать, если они вам не нужны.
Вы также можете использовать эти метаданные для фильтрации в ваших спецификациях. Скажем, у вас есть блок before, который должен работать, например, только со спецификациями модели. Ухоженная! Для больших тестовых наборов это может пригодиться в один прекрасный день. Вы можете отфильтровать, какую целевую группу тестов вы хотите запустить, вместо того, чтобы выполнять весь набор, что может занять некоторое время.
Конечно, ваши возможности выходят за рамки трех вышеупомянутых. Давайте узнаем больше о нарезке и нарезке кубиков ваших тестов в следующем разделе.
Теги
Когда вы накапливаете больший набор тестов с течением времени, вам будет недостаточно просто запускать тесты в определенных папках для быстрого и эффективного выполнения тестов RSpec. То, что вы хотите сделать, это запустить тесты, которые принадлежат друг другу, но могут быть распределены по нескольким каталогам. Теги на помощь! Не поймите меня неправильно, ключевую роль играет также организация ваших тестов в ваших папках, но с помощью тегов это продвигается немного дальше.
Вы даете своим тестам некоторые метаданные в виде символов типа «: wip», «: checkout» или того, что соответствует вашим потребностям. Когда вы запускаете эти целевые группы тестов, вы просто указываете, что RSpec должен игнорировать запуск других тестов на этот раз, предоставляя флаг с именем тегов.
Некоторый Спек Файл
1
2
3
4
5
6
7
|
describe Agent, :wip do
it ‘is a mess right now’ do
expect(agent.favorite_gadgets).to eq ‘Unknown’
end
end
|
Терминал
1
|
rspec —tag wip
|
Выход
1
2
3
4
5
6
|
Failures:
1) Agent is a mess right now
Failure/Error: expect(agent.favorite_gadgets).to eq ‘Unknown’
…
|
Вы также можете запускать всевозможные тесты и игнорировать группу групп, помеченных определенным образом. Вы просто предоставляете тильду (~) перед именем тега, и RSpec с радостью игнорирует эти тесты.
Терминал
1
|
rspec —tag ~wip
|
Запуск нескольких тегов синхронно также не является проблемой:
Терминал
1
2
3
|
rspec —tag wip —tag checkout
rspec —tag ~wip —tag checkout
|
Как вы можете видеть выше, вы можете смешивать и сочетать их по своему желанию. Синтаксис не идеален — повторение --tag
может быть не идеальным — но эй, это тоже не важно! Да, все это немного больше дополнительной работы и лишних умственных затрат при составлении спецификаций, но, с другой стороны, это действительно дает вам мощную возможность нарезать ваш набор тестов по требованию. В более крупных проектах это может сэкономить вам массу времени.
Последние мысли
То, что вы узнали до сих пор, должно дать вам абсолютные основы для самостоятельной игры с тестами — набором для выживания для начинающих. И действительно играйте и совершайте ошибки столько, сколько сможете. Возьмите RSpec и весь инструмент, управляемый тестами, и не ждите, что будете сразу же писать качественные тесты. До того, как вы почувствуете себя комфортно, и до того, как вы с ним справитесь, все еще не хватает пары деталей
Для меня это было немного неприятно в начале, потому что было трудно понять, как что-то тестировать, когда я еще не реализовал это и не до конца понимал, как это будет вести себя.
Тестирование действительно доказывает, понимаете ли вы фреймворк, такой как Rails, и знаете, как эти части сочетаются друг с другом. Когда вы пишете тесты, вы должны быть в состоянии написать ожидания относительно того, как должен вести себя фреймворк.
Это не легко, если вы только начинаете со всем этим. Работа с несколькими предметно-ориентированными языками — например, RSpec и Rails — плюс изучение Ruby API может быть адской путаницей. Не расстраивайтесь, если кривая обучения кажется пугающей; это будет легче, если вы будете придерживаться этого. Выключение этой лампочки не произойдет за ночь, но для меня это того стоило.