Статьи

Rspec- С учетом

Тестирование — это данность

Я играл с большим количеством фреймворков для тестирования. Мы, рубины, всегда зациклены на нашем тестировании. Будь то тестирование, как Усэйн Болт или TSA , в любом случае, мы тестируем. И это очень хорошая вещь.

Я подписан на то, чтобы сделать мой код максимально читаемым и понятным. Я, например, читаю больше кода, чем пишу. Первое место, которое я всегда смотрю, это тесты. Интеграционные и функциональные тесты дают мне 10 000-футовый обзор контекста, модульные тесты рассказывают мне о механике и крайних случаях. И, будучи разработчиком подобного рода, я стараюсь сделать свои тесты максимально выразительными для следующего разработчика, следуя моей истории кода. Прошли те дни накопления информации и быть специалистом в этой части кода. Оставьте это другим разработчикам. Не будь тем парнем.

В последнее время я играю с данным rspec- расширением для Rspec ни от кого другого, кроме Джима Вейриха (и я почти уверен, что мне не нужно говорить вам, кто он). Все мы знаем выразительный синтаксический сахар, который мы получаем из Rspec, и нам может понравиться ясность функций Cucumber. rspec-Given приносит в ваши тесты объявления Given, When, Then, а также пару дополнений, которые наверняка поразят мой счастливый гонг.

Основы

Получение rspec-данных в ваших спецификациях тривиально, просто добавьте гем в ваш Gemfile и потребуйте его в spec_helper.rb а-ля:

require ‘rspec’
require ‘rspec/given’

view raw
spec_helper.rb
hosted with ❤ by GitHub

И тогда все готово для нового уровня декларативного синтаксиса, который действительно подразумевает намерение тестируемого кода.

Синтаксис следует философии «дано / когда / то», 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_count1) }
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_count1) }
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_count1) }
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_count1) }
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_count1)
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)

view raw
spec_helper.rb
hosted with ❤ by GitHub

Он использует методы extend и include в Rspec::Core::Configuration . Они очень похожи по функциям, где extend добавляет к группам примеров ( describe и context ) и include в примеры (блокирует). Само по себе это очень интересная особенность Rspec, и я буду играть с ней в будущем, все благодаря rspec-данным.

Завершение

По моему мнению, rspec-данное очень полезное дополнение к набору Rspec. Из всех разработчиков, которых я знаю, используя его (и, честно говоря, сейчас его не так много), у всех есть очень хорошие вещи, чтобы сказать об этом. Это довольно компактное небольшое расширение, которое предоставляет чертовски много возможностей для спецификаций. В общем, я обнаружил, что спецификации намного аккуратнее и действительно передают смысл кода. Это очень полезные аспекты DSL, которые мы все должны стремиться создавать.

Дополнительное преимущество показывает, что такое полнофункциональный тестовый фреймворк Rspec. Стивен Бейкер и Дэвид Челимски действительно проделали огромную работу по созданию такой гибкой структуры. То, как легко Джим подключился к DSL в Rspec и продемонстрировал действительно впечатляющий синтаксис, заставляет меня бодрствовать по ночам, проклиная все их имена за то, что я чертовски хорош.