Тестирование — это данность
Я играл с большим количеством фреймворков для тестирования. Мы, рубины, всегда зациклены на нашем тестировании. Будь то тестирование, как Усэйн Болт или TSA , в любом случае, мы тестируем. И это очень хорошая вещь.
Я подписан на то, чтобы сделать мой код максимально читаемым и понятным. Я, например, читаю больше кода, чем пишу. Первое место, которое я всегда смотрю, это тесты. Интеграционные и функциональные тесты дают мне 10 000-футовый обзор контекста, модульные тесты рассказывают мне о механике и крайних случаях. И, будучи разработчиком подобного рода, я стараюсь сделать свои тесты максимально выразительными для следующего разработчика, следуя моей истории кода. Прошли те дни накопления информации и быть специалистом в этой части кода. Оставьте это другим разработчикам. Не будь тем парнем.
В последнее время я играю с данным rspec- расширением для Rspec ни от кого другого, кроме Джима Вейриха (и я почти уверен, что мне не нужно говорить вам, кто он). Все мы знаем выразительный синтаксический сахар, который мы получаем из Rspec, и нам может понравиться ясность функций Cucumber. rspec-Given приносит в ваши тесты объявления Given, When, Then, а также пару дополнений, которые наверняка поразят мой счастливый гонг.
Основы
Получение rspec-данных в ваших спецификациях тривиально, просто добавьте гем в ваш Gemfile и потребуйте его в spec_helper.rb
а-ля:
require ‘rspec’ | |
require ‘rspec/given’ |
И тогда все готово для нового уровня декларативного синтаксиса, который действительно подразумевает намерение тестируемого кода.
Синтаксис следует философии «дано / когда / то», When
Given
настроение, When
тренируешься или «тыкаешь», а Then
ожидания и утверждения.
Пример синтаксиса может быть следующим:
describe BeersOnTheWall do | |
describe «setting up the wall» do | |
context «when we are too drunk to count» do | |
Given(:beers){ BeersOnTheWall.new } | |
Then{ expect(beers.count).to eql 100} | |
end |
Вы заметите отсутствие блока When
поскольку мы только проверяем состояние, количество пива на стене, изначально. Используя знакомое describe
Rspec и блоки context
определяют, что мы тестируем. Given
блок принимает необязательный символ, который, если вы являетесь обычным пользователем Rspec, будет знаком, чтобы let(:something){ lazily_evaluate_to_something_else}
. Вы заметите шикарный новый синтаксис ожиданий и утверждений Rspec, используемый в блоке Then
.
Кстати, мне никогда не нравилось использовать слово «должен» в моих тестах. Я понимаю, что да, мы пишем это перед реализацией, поэтому мы смотрим в будущее возможностей кода. Я не знаю, это выглядит немного нечетко и почти нормально, если то, что я тестирую, является правильным только в некоторых случаях? При написании спецификации того, как должен вести себя мой код, мне нравится, что Test::Unit
assert
что это так, и теперь mintiest использует must_*
. Это четкие ожидания в отношении кода, намного превосходящие ожидания.
Тыкаешь, а что нет
Теперь мы проверили какое-то состояние в нашем коде, пришло время выполнить фрагмент кода и проверить вывод. Здесь мы используем синтаксис « When
.
context «while we still have beers» do | |
Given(:beers_on_the_wall){ BeersOnTheWall.new(beer_count) } | |
When{ beers_on_the_wall.take_one_down } | |
Then{ expect(beers_on_the_wall.count).to eql (beer_count — 1) } | |
end |
Если оставить в стороне тот факт, что я описываю не только известную рифму, вы можете прочитать этот тест и точно знать, чего мы пытаемся достичь. Настройка, тыкать, утверждать. Конечно, это явно простой пример, но мы можем расширить его с помощью нескольких настроек в нескольких блоках контекста.
describe BeersOnTheWall do | |
Given(:beer_count){ 50 } | |
describe «setting up the wall» do | |
context «when we are too drunk to count» do | |
Given(:beers){ BeersOnTheWall.new } | |
Then{ expect(beers.count).to eql 100} | |
end | |
context «when we aint even been drinking yet» do | |
Given(:beers){ BeersOnTheWall.new(beer_count) } | |
Then{ expect(beers.count).to eql beer_count} | |
end | |
end | |
describe «taking one down» do | |
Given(:beers_on_the_wall){ BeersOnTheWall.new(beer_count) } | |
When{ beers_on_the_wall.take_one_down } | |
Then{ expect(beers_on_the_wall.count).to eql (beer_count — 1) } | |
end |
Данные Rspec также содержат синтаксис And
(Примечание: мне пришлось использовать последнюю бета-версию, чтобы получить этот синтаксис, 2.1.0.beta.4). С первого взгляда And
посмотрел псевдоним Then
пока не прочитал документацию. Да, это утверждение Then
, только оно будет повторно использовать настройку обязательного Then
которая предшествует ему. Таким образом, когда у вас есть длительная настройка, вы проверили свое первое утверждение и хотите продолжить тестирование состояния, не повторяя настройку, And
это ваш друг. Что сразу приходит на ум, так это то, что когда я анализирую файлы CSV, я могу запустить установку только один раз и протестировать каждую запись, даже не перезапуская первоначальную настройку. Вряд ли потребовалась длительная настройка, но, безусловно, было бы неплохо убрать строительные леса из нового теста, когда у нас будет требуемое состояние.
Еще одна приятная особенность данного rspec — это Invariant
синтаксис. Этот блок является еще одним утверждением тогда, которое вводится в каждый контекст, применимый к нему. Invariant
предназначен для тестирования условий, которые остаются постоянными во всем тестируемом коде. Если мы вернемся к нашей песенной пьющей песне, мы могли бы написать:
describe «taking one down» do | |
Given(:beers_on_the_wall){ BeersOnTheWall.new(beer_count) } | |
Invariant{(beers_on_the_wall.count == «no more bottles of beers on the wall»).should == (beers_on_the_wall.beers == 0)} | |
When{ beers_on_the_wall.take_one_down } | |
context «while we still have beers» do | |
Then{ expect(beers_on_the_wall.count).to eql (beer_count — 1) } | |
end | |
end |
Но вы знаете, что? Я бы не стал. Не поймите меня неправильно, концепция Invariant
великолепна, и при использовании в лучшем контексте, чем пивные песни, она может быть действительно полезной. Нет, для пивных песен я лично предпочел бы пожертвовать синтаксисом для удобочитаемости и создать новый контекст теста (показывая, как легко можно изменить настройки в разных контекстах).
describe «taking one down» do | |
Given(:beers_on_the_wall){ BeersOnTheWall.new(beer_count) } | |
When{ beers_on_the_wall.take_one_down } | |
context «while we still have beers» do | |
Then{ expect(beers_on_the_wall.count).to eql (beer_count — 1) } | |
end | |
context «when all the beers have gone» do | |
Given(:beers_on_the_wall){ BeersOnTheWall.new(1) } | |
# or Given{ beers_on_the_wall.beers = 1 } | |
Then{ expect(beers_on_the_wall.count).to eql «no more bottles of beers on the wall»} | |
end | |
end |
Если мы посмотрим на документацию Rspec-Given, то увидим гораздо более подходящий пример, где утверждение гораздо яснее.
Invariant { stack.empty?.should == (stack.depth == 0) } |
С этого момента вы можете пройти тестирование всерьез. Основной момент должен быть упрощен, особенно в контексте. С чистого Rspec я бы написал:
describe «taking one down» do | |
let(:beer_count){50} | |
context «while we still have beers» do | |
it «will subtract 1 from the total number of beers» do | |
beers_on_the_wall = BeersOnTheWall.new(beer_count) | |
beers_on_the_wall.take_one_down | |
expect(beers_on_the_wall.count).to eql (beer_count — 1) | |
end | |
end | |
context «when all the beers have gone» do | |
it » will tell us there are no beers left» do | |
beers_on_the_wall = BeersOnTheWall.new(1) | |
beers_on_the_wall.take_one_down | |
expect(beers_on_the_wall.count).to eql «no more bottles of beers on the wall» | |
end | |
end | |
end |
Сравнивая их, мы можем увидеть, сколько опущено. Блоки it
становятся ненужным шумом, и чтобы высушить тестовый код, мы должны были бы реорганизовать установку в метод или помощник, настройку параметров и так далее. Использование Given
синтаксиса не только сокращает код, но и придает ему гораздо более скрытый характер.
Идем чуть глубже
Еще одна важная особенность rspec для меня — это то, что он ничего не исправляет в Rspec. Это написано как расширение. Я зашел так далеко, что создал собственные помощники и утверждения для Rspec, но никогда не расширял их.
Если мы посмотрим на конфигурацию для rspec-данным, то увидим, что она использует все возможности существующего предметно-ориентированного языка (DSL).
RSpec.configure do |c| | |
c.extend(RSpec::Given::ClassExtensions) | |
c.include(RSpec::Given::InstanceExtensions) | |
c.include(RSpec::Given::HaveFailed) |
Он использует методы extend
и include
в Rspec::Core::Configuration
. Они очень похожи по функциям, где extend
добавляет к группам примеров ( describe
и context
) и include
в примеры (блокирует). Само по себе это очень интересная особенность Rspec, и я буду играть с ней в будущем, все благодаря rspec-данным.
Завершение
По моему мнению, rspec-данное очень полезное дополнение к набору Rspec. Из всех разработчиков, которых я знаю, используя его (и, честно говоря, сейчас его не так много), у всех есть очень хорошие вещи, чтобы сказать об этом. Это довольно компактное небольшое расширение, которое предоставляет чертовски много возможностей для спецификаций. В общем, я обнаружил, что спецификации намного аккуратнее и действительно передают смысл кода. Это очень полезные аспекты DSL, которые мы все должны стремиться создавать.
Дополнительное преимущество показывает, что такое полнофункциональный тестовый фреймворк Rspec. Стивен Бейкер и Дэвид Челимски действительно проделали огромную работу по созданию такой гибкой структуры. То, как легко Джим подключился к DSL в Rspec и продемонстрировал действительно впечатляющий синтаксис, заставляет меня бодрствовать по ночам, проклиная все их имена за то, что я чертовски хорош.