RSpec — Введение
RSpec — это модуль модульного тестирования для языка программирования Ruby. RSpec отличается от традиционных платформ xUnit, таких как JUnit, потому что RSpec — это инструмент разработки, управляемый поведением. Это означает, что тесты, написанные на RSpec, фокусируются на «поведении» тестируемого приложения. RSpec делает упор не на том, как работает приложение, а на том, как оно ведет себя, другими словами, на самом деле, что делает приложение.
RSpec Environment
Прежде всего, вам нужно будет установить Ruby на ваш компьютер. Однако, если вы еще этого не сделали ранее, вы можете скачать и установить Ruby с главного сайта Ruby — Ruby .
Если вы устанавливаете Ruby в Windows, у вас должен быть установщик Ruby для Windows здесь — http://www.rubyinstaller.org
Для этого урока вам понадобится только текстовый редактор, например Блокнот и консоль командной строки. Примеры здесь будут использовать cmd.exe на Windows.
Чтобы запустить cmd.exe, просто нажмите в меню «Пуск» и введите «cmd.exe», а затем нажмите клавишу «Return».
В командной строке в окне cmd.exe введите следующую команду, чтобы увидеть, какую версию Ruby вы используете:
ruby -v
Вы должны увидеть следующий вывод, который выглядит примерно так:
ruby 2.2.3p173 (2015-08-18 revision 51636) [x64-mingw32]
Примеры в этом руководстве будут использовать Ruby 2.2.3, но подойдет любая версия Ruby выше 2.0.0. Далее нам нужно установить гем RSpec для вашей установки Ruby. Gem — это библиотека Ruby, которую вы можете использовать в своем собственном коде. Чтобы установить драгоценный камень, вам нужно использовать команду драгоценного камня .
Давайте сейчас установим гем Rspec. Вернитесь в окно cmd.exe и введите следующее —
gem install rspec
У вас должен быть список установленных драгоценных камней, это камни, которые нужны для правильной работы rspec. В конце вывода вы должны увидеть что-то похожее на это —
Done installing documentation for diff-lcs, rspec-support, rspec-mocks, rspec-expectations, rspec-core, rspec after 22 seconds 6 gems installed
Не волнуйтесь, если ваш вывод не выглядит точно так же. Кроме того, если вы используете компьютер Mac или Linux, вам может потребоваться выполнить команду gem install rspec с помощью sudo или использовать такой инструмент, как HomeBrew или RVM, чтобы установить гем rspec.
Hello World
Для начала давайте создадим каталог (папку) для хранения наших файлов RSpec. В окне cmd.exe введите следующее —
cd \
Затем введите —
mkdir rspec_tutorial
И наконец, введите —
cd rspec_tutorial
Отсюда мы собираемся создать еще один каталог с именем spec, сделав это, набрав —
mkdir spec
Мы собираемся хранить наши файлы RSpec в этой папке. Файлы RSpec известны как «спецификации». Если вас это смущает, вы можете рассматривать файл спецификации как тестовый файл. RSpec использует термин «spec», который является краткой формой для «спецификации».
Поскольку RSpec — это инструмент тестирования BDD, цель состоит в том, чтобы сосредоточиться на том, что делает приложение, и соответствует ли оно спецификации. При разработке, основанной на поведении, спецификация часто описывается в терминах «пользовательской истории». RSpec разработан, чтобы прояснить, правильно ли работает целевой код, другими словами, следуя спецификации.
Вернемся к нашему коду Hello World. Откройте текстовый редактор и добавьте следующий код —
class HelloWorld def say_hello "Hello World!" end end describe HelloWorld do context “When testing the HelloWorld class” do it "should say 'Hello World' when we call the say_hello method" do hw = HelloWorld.new message = hw.say_hello expect(message).to eq "Hello World!" end end end
Затем сохраните его в файл с именем hello_world_spec.rb в папке spec, которую вы создали выше. Теперь вернитесь в окно cmd.exe и выполните эту команду:
rspec spec spec\hello_world_spec.rb
Когда команда завершится, вы должны увидеть вывод, который выглядит следующим образом:
Finished in 0.002 seconds (files took 0.11101 seconds to load) 1 example, 0 failures
Поздравляем, вы только что создали и запустили свой первый модульный тест RSpec!
В следующем разделе мы продолжим обсуждение синтаксиса файлов RSpec.
RSpec — основной синтаксис
Давайте внимательнее посмотрим на код нашего примера HelloWorld . Прежде всего, если неясно, мы тестируем функциональность класса HelloWorld . Это, конечно, очень простой класс, который содержит только один метод say_hello () .
Вот снова код RSpec —
describe HelloWorld do context “When testing the HelloWorld class” do it "The say_hello method should return 'Hello World'" do hw = HelloWorld.new message = hw.say_hello expect(message).to eq "Hello World!" end end end
Описание Ключевое слово
Слово description является ключевым словом RSpec. Он используется для определения «группы примеров». Вы можете думать о «Группе примеров» как о наборе тестов. Ключевое слово description может принимать имя класса и / или строковый аргумент. Вам также нужно передать аргумент блока для описания , который будет содержать отдельные тесты или, как они известны в RSpec, «Примеры». Блок — это просто блок Ruby, обозначенный ключевыми словами Ruby do / end .
Ключевое слово контекста
Контекстное ключевое слово похоже на описание . Он также может принимать имя класса и / или строковый аргумент. Вы должны также использовать блок с контекстом . Идея контекста заключается в том, что он включает тесты определенного типа.
Например, вы можете указать группы примеров с различным контекстом, как это —
context “When passing bad parameters to the foobar() method” context “When passing valid parameters to the foobar() method” context “When testing corner cases with the foobar() method”
Ключевое слово context не является обязательным, но оно помогает добавить больше деталей о примерах, которые оно содержит.
Ключевое слово
Слово это другое ключевое слово RSpec, которое используется для определения «примера». Примером является в основном тест или контрольный пример. Опять же, как description и context, он принимает как имя класса, так и строковые аргументы и должен использоваться с аргументом блока, обозначенным do / end . В этом случае принято передавать только строку и аргумент блока. Строковый аргумент часто использует слово «следует» и предназначен для описания того, какое конкретное поведение должно происходить внутри блока it . Другими словами, он описывает ожидаемый результат для примера.
Обратите внимание на блок it из нашего примера HelloWorld —
it "The say_hello method should return 'Hello World'" do
Строка проясняет, что должно произойти, когда мы вызываем команду hello в экземпляре класса HelloWorld. Эта часть философии RSpec, Пример — это не просто тест, это также спецификация (спецификация). Другими словами, Пример документирует и тестирует ожидаемое поведение вашего кода Ruby.
Ожидаемое ключевое слово
Ключевое слово wait используется для определения «Ожидания» в RSpec. Это этап проверки, на котором мы проверяем, что определенное ожидаемое условие выполнено.
Из нашего примера HelloWorld, мы имеем —
expect(message).to eql "Hello World!"
Идея с ожидаемыми утверждениями заключается в том, что они читаются как нормальный английский. Вы можете сказать это вслух как «Ожидайте, что переменное сообщение будет равно строке« Hello World »». Идея заключается в том, что он описательный, а также легко читаемый, даже для нетехнических заинтересованных сторон, таких как менеджеры проектов.
The to keyword
Ключевое слово to используется как часть ожиданий . Обратите внимание, что вы также можете использовать ключевое слово not_to, чтобы выразить обратное, когда вы хотите, чтобы Ожидание было ложным. Вы можете видеть, что to используется с точкой, ожидаем (message) .to, потому что это на самом деле обычный метод Ruby. На самом деле все ключевые слова RSpec на самом деле являются просто методами Ruby.
The eql keyword
Ключевое слово eql — это специальное ключевое слово RSpec, называемое Matcher. Вы используете Matchers, чтобы указать, какой тип условия вы проверяете, чтобы быть истинным (или ложным).
В нашем ожидании HelloWorld ясно, что eql означает равенство строк. Обратите внимание, что в Ruby существуют различные типы операторов равенства и, следовательно, разные соответствующие Matchers в RSpec. Мы рассмотрим множество различных типов Matchers в следующем разделе.
RSpec — Написание спецификаций
В этой главе мы создадим новый класс Ruby, сохраним его в своем собственном файле и создадим отдельный файл спецификации для тестирования этого класса.
Во-первых, в нашем новом классе он называется StringAnalyzer . Это простой класс, который, как вы уже догадались, анализирует строки. В нашем классе есть только один метод has_vowels? который, как следует из его имен, возвращает true, если строка содержит гласные, и false, если нет. Вот реализация для StringAnalyzer —
class StringAnalyzer def has_vowels?(str) !!(str =~ /[aeio]+/i) end end
Если вы следовали разделу HelloWorld, вы создали папку с именем C: \ rspec_tutorial \ spec.
Удалите файл hello_world.rb, если он у вас есть, и сохраните приведенный выше код StringAnalyzer в файл с именем string_analyzer.rb в папке C: \ rspec_tutorial \ spec.
Вот источник нашего спецификационного файла для тестирования StringAnalyzer —
require 'string_analyzer' describe StringAnalyzer do context "With valid input" do it "should detect when a string contains vowels" do sa = StringAnalyzer.new test_string = 'uuu' expect(sa.has_vowels? test_string).to be true end it "should detect when a string doesn't contain vowels" do sa = StringAnalyzer.new test_string = 'bcdfg' expect(sa.has_vowels? test_string).to be false end end end
Сохраните это в той же директории спецификации, присвоив ей имя string_analyzer_test.rb.
В окне cmd.exe перейдите в папку C: \ rspec_tutorial и выполните следующую команду: dir spec
Вы должны увидеть следующее —
Каталог C: \ rspec_tutorial \ spec
09/13/2015 08:22 AM <DIR> . 09/13/2015 08:22 AM <DIR> .. 09/12/2015 11:44 PM 81 string_analyzer.rb 09/12/2015 11:46 PM 451 string_analyzer_test.rb
Теперь мы собираемся запустить наши тесты, запустите эту команду: rspec spec
Когда вы передаете имя папки в rspec , она запускает все файлы спецификации внутри папки. Вы должны увидеть этот результат —
No examples found. Finished in 0 seconds (files took 0.068 seconds to load) 0 examples, 0 failures
Причина этого заключается в том, что по умолчанию rspec запускает только файлы, имена которых заканчиваются на «_spec.rb». Переименуйте string_analyzer_test.rb в string_analyzer_spec.rb. Вы можете сделать это легко, запустив эту команду —
ren spec\string_analyzer_test.rb string_analyzer_spec.rb
Теперь, запустите rspec spec снова, вы должны увидеть вывод, который выглядит следующим образом:
F. Failures: 1) StringAnalyzer With valid input should detect when a string contains vowels Failure/Error: expect(sa.has_vowels? test_string).to be true expected true got false # ./spec/string_analyzer_spec.rb:9:in `block (3 levels) in <top (required)>' Finished in 0.015 seconds (files took 0.12201 seconds to load) 2 examples, 1 failure Failed examples: rspec ./spec/string_analyzer_spec.rb:6 # StringAnalyzer With valid input should detect when a string contains vowels Do you see what just happened? Our spec failed because we have a bug in StringAnalyzer. The bug is simple to fix, open up string_analyzer.rb in a text editor and change this line: !!(str =~ /[aeio]+/i) to this: !!(str =~ /[aeiou]+/i)
Теперь сохраните изменения, которые вы только что внесли в string_analyizer.rb, и снова запустите команду спецификации rspec, теперь вы должны увидеть вывод, который выглядит как —
.. Finished in 0.002 seconds (files took 0.11401 seconds to load) 2 examples, 0 failures
Поздравляем, примеры (тесты) в вашем спецификационном файле теперь проходят. Мы исправили ошибку в регулярном выражении, которое имеет метод гласных, но наши тесты далеки от завершения.
Было бы целесообразно добавить больше примеров, которые проверяют различные типы входных строк с помощью метода has vowels.
В следующей таблице приведены некоторые перестановки, которые можно добавить в новые примеры (блокируются).
Строка ввода | Описание | Ожидаемый результат с has_vowels? |
---|---|---|
‘ааа’, ‘э-э-э’, ‘iii’, ‘о’ | Только одна гласная и никаких других букв. | правда |
‘Abcefg’ | «По крайней мере, один гласный и несколько согласных» | правда |
‘Mnklp’ | Только согласные. | ложный |
«» | Пустая строка (без букв) | ложный |
‘Abcde55345 & ??’ | Гласные, согласные, цифры и знаки препинания. | правда |
‘423432 %%% ^ &’ | Только цифры и знаки препинания. | ложный |
‘AEIOU’ | Только верхний регистр гласных. | правда |
‘AeiOuuuA’ | Только верхний регистр и нижние гласные. | правда |
‘ABCDEFGHI’ | Верхний и нижний регистр гласных и согласных. | правда |
‘BCDFG’ | Только в верхнем регистре. | ложный |
» | Только пробельные символы. | ложный |
Вам решать, какие примеры добавить в ваш файл спецификаций. Есть много условий для тестирования, вам нужно определить, какое подмножество условий является наиболее важным и тестирует ваш код лучше всего.
Команда rspec предлагает много разных опций, чтобы увидеть их все, наберите rspec -help. В следующей таблице перечислены наиболее популярные варианты и описано, что они делают.
Sr.No. | Опция / флаг и описание |
---|---|
1 |
-Я ПУТЬ Добавляет PATH к пути загрузки (требуется), который rspec использует при поиске исходных файлов Ruby. |
2 |
-r, -require PATH Добавляет определенный исходный файл, который требуется в вашей спецификации. файл (ы). |
3 |
—fail-быстро С этой опцией rspec прекратит запуск спецификаций после сбоя первого примера. По умолчанию rspec запускает все указанные файлы спецификаций, независимо от количества сбоев. |
4 |
-f, —формат FORMATTER Эта опция позволяет вам указать разные выходные форматы. См. Раздел «Форматтеры» для более подробной информации о форматах вывода. |
5 |
-о, -из ФАЙЛА Эта опция указывает rspec записывать результаты теста в выходной файл FILE вместо стандартного вывода. |
6 |
-c, —color Включает цвет в выводе rspec. Успешные примеры будут отображаться зеленым цветом, ошибки — красным. |
7 |
-b, —backtrace Отображает полные ошибки в выводе rspec. |
8 |
-w, — предупреждения Отображает предупреждения Ruby в выводе rspec. |
9 |
-P, —pattern PATTERN Загрузите и запустите файлы спецификаций, которые соответствуют шаблону PATTERN. Например, если вы передадите -p «* .rb», rspec запустит все файлы Ruby, а не только те, которые заканчиваются на «_spec.rb». |
10 |
-e, — пример STRING Эта опция указывает rspec запускать все Примеры, которые содержат текст STRING в своих описаниях. |
11 |
-t, —tag TAG С этой опцией rspec будет запускать только примеры, содержащие тег TAG. Обратите внимание, что TAG указан как символ Ruby. См. Раздел «Теги RSpec» для более подробной информации. |
-Я ПУТЬ
Добавляет PATH к пути загрузки (требуется), который rspec использует при поиске исходных файлов Ruby.
-r, -require PATH
Добавляет определенный исходный файл, который требуется в вашей спецификации. файл (ы).
—fail-быстро
С этой опцией rspec прекратит запуск спецификаций после сбоя первого примера. По умолчанию rspec запускает все указанные файлы спецификаций, независимо от количества сбоев.
-f, —формат FORMATTER
Эта опция позволяет вам указать разные выходные форматы. См. Раздел «Форматтеры» для более подробной информации о форматах вывода.
-о, -из ФАЙЛА
Эта опция указывает rspec записывать результаты теста в выходной файл FILE вместо стандартного вывода.
-c, —color
Включает цвет в выводе rspec. Успешные примеры будут отображаться зеленым цветом, ошибки — красным.
-b, —backtrace
Отображает полные ошибки в выводе rspec.
-w, — предупреждения
Отображает предупреждения Ruby в выводе rspec.
-P, —pattern PATTERN
Загрузите и запустите файлы спецификаций, которые соответствуют шаблону PATTERN. Например, если вы передадите -p «* .rb», rspec запустит все файлы Ruby, а не только те, которые заканчиваются на «_spec.rb».
-e, — пример STRING
Эта опция указывает rspec запускать все Примеры, которые содержат текст STRING в своих описаниях.
-t, —tag TAG
С этой опцией rspec будет запускать только примеры, содержащие тег TAG. Обратите внимание, что TAG указан как символ Ruby. См. Раздел «Теги RSpec» для более подробной информации.
RSpec — Matchers
Если вы помните наш оригинальный пример Hello World, он содержал строку, которая выглядела так:
expect(message).to eq "Hello World!"
Ключевое слово eql — это RSpec «matcher». Здесь мы представим другие типы сопоставителей в RSpec.
Равенство / Идентичность
Сопоставители для проверки на предмет или равенство значений.
Сличитель | Описание | пример |
---|---|---|
уравнение | Проходит, когда актуально == ожидается | ожидаемый (фактический). к ожидаемому |
EQL | Проходит, когда актуально. | ожидаемый (фактический). до ожидаемого |
быть | Проходит, когда актуально. | ожидать (факт). быть ожидаемым |
равный | Также проходит, когда фактический.?? (Ожидается) | ожидаемый (фактический). равный ожидаемому |
пример
describe "An example of the equality Matchers" do it "should show how the equality Matchers work" do a = "test string" b = a # The following Expectations will all pass expect(a).to eq "test string" expect(a).to eql "test string" expect(a).to be b expect(a).to equal b end end
Когда приведенный выше код будет выполнен, он выдаст следующий вывод. Количество секунд может немного отличаться на вашем компьютере —
. Finished in 0.036 seconds (files took 0.11901 seconds to load) 1 example, 0 failures
Сравнители
Сопоставители для сравнения со значениями.
Сличитель | Описание | пример |
---|---|---|
> | Проходит, когда факт> ожидается | ожидать (фактическое). быть> ожидаемым |
> = | Проходит, когда актуально> = ожидается | ожидать (фактическое). быть> = ожидается |
< | Проходит, когда фактический <ожидается | ожидать (фактическое). быть <ожидается |
<= | Проходит, когда фактический <= ожидаемый | ожидать (фактическое). быть <= ожидается |
be_between включительно | Проходит, когда фактическое значение <= min и> = max | ожидаемый (фактический) .в be_between (min, max) .inclusive |
be_between эксклюзив | Проходит, когда фактический <min и> max | ожидаемый (фактический) .в be_between (min, max) .exclusive |
матч | Проходит, когда фактическое соответствует регулярному выражению | ожидать (фактическое). к совпадению (/ regex /) |
пример
describe "An example of the comparison Matchers" do it "should show how the comparison Matchers work" do a = 1 b = 2 c = 3 d = 'test string' # The following Expectations will all pass expect(b).to be > a expect(a).to be >= a expect(a).to be < b expect(b).to be <= b expect(c).to be_between(1,3).inclusive expect(b).to be_between(1,3).exclusive expect(d).to match /TEST/i end end
Когда приведенный выше код будет выполнен, он выдаст следующий вывод. Количество секунд может немного отличаться на вашем компьютере —
. Finished in 0.013 seconds (files took 0.11801 seconds to load) 1 example, 0 failures
Сопоставители классов / типов
Сопоставители для проверки типа или класса объектов.
Сличитель | Описание | пример |
---|---|---|
be_instance_of | Проходит, когда фактический является экземпляром ожидаемого класса. | ожидаемый (фактический) .в be_instance_of (ожидаемый) |
be_kind_of | Проходит, когда фактический является экземпляром ожидаемого класса или любого из его родительских классов. | ожидаемый (фактический) .to be_kind_of (ожидаемый) |
respond_to | Проходит, когда фактическое отвечает на указанный метод. | ожидаемый (фактический) .to response_to (ожидаемый) |
пример
describe "An example of the type/class Matchers" do it "should show how the type/class Matchers work" do x = 1 y = 3.14 z = 'test string' # The following Expectations will all pass expect(x).to be_instance_of Fixnum expect(y).to be_kind_of Numeric expect(z).to respond_to(:length) end end
Когда приведенный выше код будет выполнен, он выдаст следующий вывод. Количество секунд может немного отличаться на вашем компьютере —
. Finished in 0.002 seconds (files took 0.12201 seconds to load) 1 example, 0 failures
True / False / Nil Matchers
Соответствует для проверки, является ли значение истинным, ложным или нулевым.
Сличитель | Описание | пример |
---|---|---|
быть правдой | Проходит, когда актуально == правда | ожидать (фактическое). быть правдой |
быть ложным | Проходит, когда актуально == ложно | ожидать (фактическое). быть ложным |
be_truthy | Проходит, когда фактический не ложь или ноль | ожидать (фактическое). to be_truthy |
be_falsey | Проходит, когда фактическое значение равно false или равно нулю | ожидать (фактический) .в be_falsey |
be_nil | Проходит, когда фактический ноль | ожидать (фактический) .в be_nil |
пример
describe "An example of the true/false/nil Matchers" do it "should show how the true/false/nil Matchers work" do x = true y = false z = nil a = "test string" # The following Expectations will all pass expect(x).to be true expect(y).to be false expect(a).to be_truthy expect(z).to be_falsey expect(z).to be_nil end end
Когда приведенный выше код будет выполнен, он выдаст следующий вывод. Количество секунд может немного отличаться на вашем компьютере —
. Finished in 0.003 seconds (files took 0.12301 seconds to load) 1 example, 0 failures
Сопоставление ошибок
Сопоставители для тестирования, когда блок кода вызывает ошибку.
Сличитель | Описание | пример |
---|---|---|
raise_error (ErrorClass) | Проходит, когда блок вызывает ошибку типа ErrorClass. | ожидать {блок} .в поднять_ошибку (ErrorClass) |
повышение_ответа («сообщение об ошибке») | Проходит, когда блок выдает ошибку с сообщением «error error». | Ожидаем {block} .to повысить_error («сообщение об ошибке») |
повышение_еррор (ErrorClass, «сообщение об ошибке») | Проходит, когда блок выдает ошибку типа ErrorClass с сообщением «error error» | wait {block} .to повысить_error (ErrorClass, «сообщение об ошибке») |
пример
Сохраните следующий код в файл с именем error_matcher_spec.rb и запустите его с помощью этой команды — rspec error_matcher_spec.rb .
describe "An example of the error Matchers" do it "should show how the error Matchers work" do # The following Expectations will all pass expect { 1/0 }.to raise_error(ZeroDivisionError) expect { 1/0 }.to raise_error("divided by 0") expect { 1/0 }.to raise_error("divided by 0", ZeroDivisionError) end end
Когда приведенный выше код будет выполнен, он выдаст следующий вывод. Количество секунд может немного отличаться на вашем компьютере —
. Finished in 0.002 seconds (files took 0.12101 seconds to load) 1 example, 0 failures
RSpec — Test Doubles
В этой главе мы обсудим RSpec Doubles, также известный как RSpec Mocks. Двойник — это объект, который может «стоять» за другим объектом. Вам, наверное, интересно, что именно это означает и зачем вам это нужно.
Допустим, вы создаете приложение для школы, и у вас есть класс, представляющий класс учащихся, и другой класс для учащихся, то есть у вас есть класс и класс ученика. Сначала вам нужно написать код для одного из классов, так что давайте начнем с класса Classroom —
class ClassRoom def initialize(students) @students = students end def list_student_names @students.map(&:name).join(',') end end
Это простой класс, у него есть один метод list_student_names, который возвращает разделенную запятыми строку имен учеников. Теперь мы хотим создать тесты для этого класса, но как нам это сделать, если мы еще не создали класс Student? Нам нужен тест Double.
Кроме того, если у нас есть «фиктивный» класс, который ведет себя как объект Student, то наши тесты ClassRoom не будут зависеть от класса Student. Мы называем это тестом изоляции.
Если наши тесты ClassRoom не полагаются на какие-либо другие классы, то, когда тест не пройден, мы сразу можем знать, что в нашем классе ClassRoom есть ошибка, а не какой-то другой класс. Имейте в виду, что в реальном мире вы можете создавать класс, который должен взаимодействовать с другим классом, написанным кем-то другим.
Именно здесь RSpec Doubles (mocks) становятся полезными. Наш метод list_student_names вызывает метод name для каждого объекта Student в его переменной-члене @students. Поэтому нам нужен Double, который реализует метод имени.
Вот код для ClassRoom вместе с примером RSpec (test), но обратите внимание, что класс Student не определен —
class ClassRoom def initialize(students) @students = students end def list_student_names @students.map(&:name).join(',') end end describe ClassRoom do it 'the list_student_names method should work correctly' do student1 = double('student') student2 = double('student') allow(student1).to receive(:name) { 'John Smith'} allow(student2).to receive(:name) { 'Jill Smith'} cr = ClassRoom.new [student1,student2] expect(cr.list_student_names).to eq('John Smith,Jill Smith') end end
Когда приведенный выше код будет выполнен, он выдаст следующий вывод. Прошедшее время может немного отличаться на вашем компьютере —
. Finished in 0.01 seconds (files took 0.11201 seconds to load) 1 example, 0 failures
Как вы можете видеть, использование двойного теста позволяет вам тестировать ваш код, даже если он использует класс, который не определен или недоступен. Кроме того, это означает, что при сбое теста вы можете сразу сказать, что это связано с проблемой в вашем классе, а не с классом, написанным кем-то другим.
RSpec — заглушки
Если вы уже читали раздел о RSpec Doubles (он же Mocks), значит, вы уже видели заглушки RSpec. В RSpec заглушку часто называют заглушкой метода, это особый тип метода, который «заменяет» существующий метод или метод, который еще даже не существует.
Вот код из раздела на RSpec Doubles —
class ClassRoom def initialize(students) @students = students End def list_student_names @students.map(&:name).join(',') end end describe ClassRoom do it 'the list_student_names method should work correctly' do student1 = double('student') student2 = double('student') allow(student1).to receive(:name) { 'John Smith'} allow(student2).to receive(:name) { 'Jill Smith'} cr = ClassRoom.new [student1,student2] expect(cr.list_student_names).to eq('John Smith,Jill Smith') end end
В нашем примере метод allow () предоставляет заглушки метода, которые нам нужны для проверки класса ClassRoom. В этом случае нам нужен объект, который будет действовать как экземпляр класса Student, но этот класс на самом деле не существует (пока). Мы знаем, что класс Student должен предоставить метод name (), и мы используем allow () для создания заглушки метода для name ().
Стоит отметить, что синтаксис RSpec с годами немного изменился. В более старых версиях RSpec вышеприведенные заглушки метода были бы определены так:
student1.stub(:name).and_return('John Smith') student2.stub(:name).and_return('Jill Smith')
Давайте возьмем приведенный выше код и заменим две строки allow () старым синтаксисом RSpec —
class ClassRoom def initialize(students) @students = students end def list_student_names @students.map(&:name).join(',') end end describe ClassRoom do it 'the list_student_names method should work correctly' do student1 = double('student') student2 = double('student') student1.stub(:name).and_return('John Smith') student2.stub(:name).and_return('Jill Smith') cr = ClassRoom.new [student1,student2] expect(cr.list_student_names).to eq('John Smith,Jill Smith') end end
Вы увидите этот вывод, когда вы выполните приведенный выше код —
. Deprecation Warnings: Using `stub` from rspec-mocks' old `:should` syntax without explicitly enabling the syntax is deprec ated. Use the new `:expect` syntax or explicitly enable `:should` instead. Called from C:/rspec_tuto rial/spec/double_spec.rb:15:in `block (2 levels) in <top (required)>'. If you need more of the backtrace for any of these deprecations to identify where to make the necessary changes, you can configure `config.raise_errors_for_deprecations!`, and it will turn the deprecation warnings into errors, giving you the full backtrace. 1 deprecation warning total Finished in 0.002 seconds (files took 0.11401 seconds to load) 1 example, 0 failures
Рекомендуется использовать новый синтаксис allow (), когда вам нужно создать заглушки методов в ваших примерах RSpec, но мы предоставили здесь более старый стиль, чтобы вы могли его распознать, если увидите.
RSpec — крючки
Когда вы пишете модульные тесты, часто удобно запускать код установки и удаления до и после ваших тестов. Код настройки — это код, который настраивает или «устанавливает» условия для теста. Разрушающий код выполняет очистку, он гарантирует, что среда находится в согласованном состоянии для последующих тестов.
Вообще говоря, ваши тесты должны быть независимы друг от друга. Когда вы запускаете весь набор тестов, и один из них дает сбой, вы хотите быть уверены, что он потерпел неудачу, потому что в тестируемом коде есть ошибка, а не потому, что предыдущий тест оставил среду в несовместимом состоянии.
Наиболее распространенные хуки, используемые в RSpec — это хуки до и после. Они предоставляют способ определения и запуска кода установки и разрыва, который мы обсуждали выше. Давайте рассмотрим этот пример кода —
class SimpleClass attr_accessor :message def initialize() puts "\nCreating a new instance of the SimpleClass class" @message = 'howdy' end def update_message(new_message) @message = new_message end end describe SimpleClass do before(:each) do @simple_class = SimpleClass.new end it 'should have an initial message' do expect(@simple_class).to_not be_nil @simple_class.message = 'Something else. . .' end it 'should be able to change its message' do @simple_class.update_message('a new message') expect(@simple_class.message).to_not be 'howdy' end end
Когда вы запустите этот код, вы получите следующий вывод:
Creating a new instance of the SimpleClass class . Creating a new instance of the SimpleClass class . Finished in 0.003 seconds (files took 0.11401 seconds to load) 2 examples, 0 failures
Давайте внимательнее посмотрим на то, что происходит. Метод before (: each) — это место, где мы определяем код установки. Когда вы передаете аргумент: each, вы инструктируете метод before запускаться перед каждым примером в вашей группе примеров, т.е. двумя блоками it внутри блока description в приведенном выше коде.
В строке: @simple_class = SimpleClass.new мы создаем новый экземпляр класса SimpleClass и присваиваем его переменной экземпляра объекта. Какой объект вам может быть интересно? RSpec создает специальный класс за кулисами в области блока описания. Это позволяет вам присваивать значения переменным экземпляра этого класса, к которым вы можете обращаться внутри блоков it в ваших примерах. Это также облегчает написание более чистого кода в наших тестах. Если для каждого теста (примера) требуется экземпляр SimpleClass, мы можем поместить этот код в ловушку before и не добавлять его в каждый пример.
Обратите внимание, что строка «Создание нового экземпляра класса SimpleClass» записывается в консоль дважды, это показывает, что до вызова ловушки в каждом из блоков it .
Как мы уже упоминали, RSpec также имеет хук после и оба хука до и после могут принимать: все в качестве аргумента. Хук после будет работать после указанной цели. Цель: all означает, что ловушка будет выполняться до / после всех примеров. Вот простой пример, который иллюстрирует, когда вызывается каждый хук.
describe "Before and after hooks" do before(:each) do puts "Runs before each Example" end after(:each) do puts "Runs after each Example" end before(:all) do puts "Runs before all Examples" end after(:all) do puts "Runs after all Examples" end it 'is the first Example in this spec file' do puts 'Running the first Example' end it 'is the second Example in this spec file' do puts 'Running the second Example' end end
Когда вы запустите приведенный выше код, вы увидите этот вывод —
Runs before all Examples Runs before each Example Running the first Example Runs after each Example .Runs before each Example Running the second Example Runs after each Example .Runs after all Examples
RSpec — Теги
Теги RSpec предоставляют простой способ запуска определенных тестов в ваших спецификационных файлах. По умолчанию RSpec запускает все тесты в специфицированных файлах, которые он запускает, но вам может потребоваться запустить только их подмножество. Допустим, у вас есть несколько тестов, которые выполняются очень быстро, и вы только что внесли изменения в код своего приложения и хотите просто запустить быстрые тесты, этот код продемонстрирует, как это сделать с помощью тегов RSpec.
describe "How to run specific Examples with Tags" do it 'is a slow test', :slow = > true do sleep 10 puts 'This test is slow!' end it 'is a fast test', :fast = > true do puts 'This test is fast!' end end
Теперь сохраните приведенный выше код в новый файл с именем tag_spec.rb. Из командной строки выполните эту команду: rspec —tag slow tag_spec.rb
Вы увидите этот вывод —
Параметры запуска: include {: slow => true}
This test is slow! . Finished in 10 seconds (files took 0.11601 seconds to load) 1 example, 0 failures
Затем выполните эту команду: rspec —tag fast tag_spec.rb
Вы увидите этот вывод —
Run options: include {:fast = >true} This test is fast! . Finished in 0.001 seconds (files took 0.11201 seconds to load) 1 example, 0 failures
Как видите, теги RSpec упрощают проведение подмножества тестов!
RSpec — Предметы
Одна из сильных сторон RSpec заключается в том, что она предоставляет множество способов написания тестов, очистки тестов. Когда ваши тесты короткие и беспорядочные, становится легче сосредоточиться на ожидаемом поведении, а не на деталях написания тестов. Предметы RSpec — это еще один ярлык, позволяющий писать простые простые тесты.
Рассмотрим этот код —
class Person attr_reader :first_name, :last_name def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end end describe Person do it 'create a new person with a first and last name' do person = Person.new 'John', 'Smith' expect(person).to have_attributes(first_name: 'John') expect(person).to have_attributes(last_name: 'Smith') end end
Это на самом деле довольно ясно, как есть, но мы могли бы использовать функцию субъекта RSpec, чтобы уменьшить количество кода в примере. Мы делаем это, перемещая экземпляр объекта person в строку описания.
class Person attr_reader :first_name, :last_name def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end end describe Person.new 'John', 'Smith' do it { is_expected.to have_attributes(first_name: 'John') } it { is_expected.to have_attributes(last_name: 'Smith') } end
Когда вы запустите этот код, вы увидите этот вывод —
.. Finished in 0.003 seconds (files took 0.11201 seconds to load) 2 examples, 0 failures
Обратите внимание, насколько проще второй пример кода. Мы взяли один блок it в первом примере и заменили его двумя блоками it, которые в итоге требуют меньше кода и так же понятны.
RSpec — Помощники
Иногда вашим примерам RSpec нужен простой способ поделиться повторно используемым кодом. Лучший способ сделать это с помощью помощников. Помощники — это обычные методы Ruby, которыми вы делитесь в примерах. Чтобы проиллюстрировать преимущества использования помощников, давайте рассмотрим этот код —
class Dog attr_reader :good_dog, :has_been_walked def initialize(good_or_not) @good_dog = good_or_not @has_been_walked = false end def walk_dog @has_been_walked = true end end describe Dog do it 'should be able to create and walk a good dog' do dog = Dog.new(true) dog.walk_dog expect(dog.good_dog).to be true expect(dog.has_been_walked).to be true end it 'should be able to create and walk a bad dog' do dog = Dog.new(false) dog.walk_dog expect(dog.good_dog).to be false expect(dog.has_been_walked).to be true end end
Этот код понятен, но всегда полезно сокращать повторяющийся код, когда это возможно. Мы можем взять приведенный выше код и уменьшить часть этого повторения с помощью вспомогательного метода с именем create_and_walk_dog ().
class Dog attr_reader :good_dog, :has_been_walked def initialize(good_or_not) @good_dog = good_or_not @has_been_walked = false end def walk_dog @has_been_walked = true end end describe Dog do def create_and_walk_dog(good_or_bad) dog = Dog.new(good_or_bad) dog.walk_dog return dog end it 'should be able to create and walk a good dog' do dog = create_and_walk_dog(true) expect(dog.good_dog).to be true expect(dog.has_been_walked).to be true end it 'should be able to create and walk a bad dog' do dog = create_and_walk_dog(false) expect(dog.good_dog).to be false expect(dog.has_been_walked).to be true end end
Когда вы запустите приведенный выше код, вы увидите этот вывод —
.. Finished in 0.002 seconds (files took 0.11401 seconds to load) 2 examples, 0 failures
Как видите, мы смогли продвинуть логику создания и перемещения объекта собаки в помощника, что позволяет нашим примерам быть короче и чище.
RSpec — метаданные
RSpec — гибкий и мощный инструмент. Функциональность метаданных в RSpec не является исключением. Метаданные обычно относятся к «данным о данных». В RSpec это означает данные о вашем описании , контексте и блокировках .
Давайте посмотрим на пример —
RSpec.describe "An Example Group with a metadata variable", :foo => 17 do context 'and a context with another variable', :bar => 12 do it 'can access the metadata variable of the outer Example Group' do |example| expect(example.metadata[:foo]).to eq(17) end it 'can access the metadata variable in the context block' do |example| expect(example.metadata[:bar]).to eq(12) end end end
Когда вы запустите приведенный выше код, вы увидите этот вывод —
.. Finished in 0.002 seconds (files took 0.11301 seconds to load) 2 examples, 0 failures
Метаданные позволяют назначать переменные в различных областях в файлах RSpec. Переменная example.metadata — это хэш Ruby, который содержит другую информацию о ваших примерах и группах примеров.
Например, давайте перепишем приведенный выше код, чтобы он выглядел так:
RSpec.describe "An Example Group with a metadata variable", :foo => 17 do context 'and a context with another variable', :bar => 12 do it 'can access the metadata variable in the context block' do |example| expect(example.metadata[:foo]).to eq(17) expect(example.metadata[:bar]).to eq(12) example.metadata.each do |k,v| puts "#{k}: #{v}" end end end
Когда мы запускаем этот код, мы видим все значения в хеше example.metadata —
.execution_result: #<RSpec::Core::Example::ExecutionResult:0x00000002befd50> block: #<Proc:0x00000002bf81a8@C:/rspec_tutorial/spec/metadata_spec.rb:7> description_args: ["can access the metadata variable in the context block"] description: can access the metadata variable in the context block full_description: An Example Group with a metadata variable and a context with another variable can access the metadata variable in the context block described_class: file_path: ./metadata_spec.rb line_number: 7 location: ./metadata_spec.rb:7 absolute_file_path: C:/rspec_tutorial/spec/metadata_spec.rb rerun_file_path: ./metadata_spec.rb scoped_id: 1:1:2 foo: 17 bar: 12 example_group: {:execution_result=>#<RSpec::Core::Example::ExecutionResult: 0x00000002bfa0e8>, :block=>#< Proc:0x00000002bfac00@C:/rspec_tutorial/spec/metadata_spec.rb:2>, :description_args=>["and a context with another variable"], :description=>"and a context with another variable", :full_description=>"An Example Group with a metadata variable and a context with another variable", :described_class=>nil, :file_path=>"./metadata_spec.rb", :line_number=>2, :location=>"./metadata_spec.rb:2", :absolute_file_path=>"C:/rspec_tutorial/spec/metadata_spec.rb", :rerun_file_path=>"./metadata_spec.rb", :scoped_id=>"1:1", :foo=>17, :parent_example_group=> {:execution_result=>#< RSpec::Core::Example::ExecutionResult:0x00000002c1f690>, :block=>#<Proc:0x00000002baff70@C:/rspec_tutorial/spec/metadata_spec.rb:1> , :description_args=>["An Example Group with a metadata variable"], :description=>"An Example Group with a metadata variable", :full_description=>"An Example Group with a metadata variable", :described_class=>nil, :file_path=>"./metadata_spec.rb", :line_number=>1, :location=>"./metadata_spec.rb:1", :absolute_file_path=> "C:/rspec_tutorial/spec/metadata_spec.rb", :rerun_file_path=>"./metadata_spec.rb", :scoped_id=>"1", :foo=>17}, :bar=>12}shared_group_inclusion_backtrace: [] last_run_status: unknown . . Finished in 0.004 seconds (files took 0.11101 seconds to load) 2 examples, 0 failures
Скорее всего, вам не нужно будет использовать все эти метаданные, но посмотрите полное значение описания —
Группа примеров с переменной метаданных и контекстом с другой переменной может обращаться к переменной метаданных в блоке контекста.
Это предложение, созданное на основе описания блока описания + содержащегося в нем описания блока контекста + описания блока it .
Интересно отметить, что эти три строки читаются как обычное английское предложение. , , которая является одной из идей RSpec, имея тесты, которые звучат как английские описания поведения.
RSpec — фильтрация
Возможно, вы захотите прочитать раздел о метаданных RSpec, прежде чем читать этот раздел, потому что, как оказалось, фильтрация RSpec основана на метаданных RSpec.
Представьте, что у вас есть файл спецификации, и он содержит два типа тестов (Примеры): положительные функциональные тесты и отрицательные (ошибки) тесты. Давайте определим их так:
RSpec.describe "An Example Group with positive and negative Examples" do context 'when testing Ruby\'s build-in math library' do it 'can do normal numeric operations' do expect(1 + 1).to eq(2) end it 'generates an error when expected' do expect{1/0}.to raise_error(ZeroDivisionError) end end end
Теперь сохраните приведенный выше текст в виде файла с именем filter_spec.rb и запустите его с помощью этой команды:
rspec filter_spec.rb
Вы увидите вывод, который выглядит примерно так:
.. Finished in 0.003 seconds (files took 0.11201 seconds to load) 2 examples, 0 failures
А что если мы захотим повторно запустить только положительные тесты в этом файле? Или только отрицательные тесты? Мы можем легко сделать это с RSpec Filters. Измените приведенный выше код на это —
RSpec.describe "An Example Group with positive and negative Examples" do context 'when testing Ruby\'s build-in math library' do it 'can do normal numeric operations', positive: true do expect(1 + 1).to eq(2) end it 'generates an error when expected', negative: true do expect{1/0}.to raise_error(ZeroDivisionError) end end end
Сохраните ваши изменения в filter_spec.rb и выполните эту немного другую команду —
rspec --tag positive filter_spec.rb
Теперь вы увидите вывод, который выглядит следующим образом —
Run options: include {:positive=>true} . Finished in 0.001 seconds (files took 0.11401 seconds to load) 1 example, 0 failures
Указывая —tag positive, мы говорим RSpec запускать примеры только с определенной переменной метаданных: positive. Мы могли бы сделать то же самое с отрицательными тестами, запустив такую команду:
rspec --tag negative filter_spec.rb
Имейте в виду, что это только примеры, вы можете указать фильтр с любым именем, которое вы хотите.
Форматеры RSpec
Форматеры позволяют RSpec отображать результаты тестов различными способами. Давайте создадим новый файл RSpec, содержащий этот код —
RSpec.describe "A spec file to demonstrate how RSpec Formatters work" do context 'when running some tests' do it 'the test usually calls the expect() method at least once' do expect(1 + 1).to eq(2) end end end
Теперь сохраните это в файл с именем formatter_spec.rb и выполните команду RSpec —
rspec formatter_spec.rb
Вы должны увидеть результат, который выглядит следующим образом —
. Finished in 0.002 seconds (files took 0.11401 seconds to load) 1 example, 0 failures
Теперь запустите ту же команду, но на этот раз укажите форматер, например:
rspec --format progress formatter_spec.rb
Вы должны увидеть тот же результат на этот раз —
. Finished in 0.002 seconds (files took 0.11401 seconds to load) 1 example, 0 failures
Причина в том, что форматер «progress» является форматером по умолчанию. Давайте попробуем другой форматтер дальше, попробуйте запустить эту команду —
rspec --format doc formatter_spec.rb
Теперь вы должны увидеть этот вывод —
A spec file to demonstrate how RSpec Formatters work when running some tests the test usually calls the expect() method at least once Finished in 0.002 seconds (files took 0.11401 seconds to load) 1 example, 0 failures
Как вы можете видеть, выходные данные в формататоре «doc» сильно отличаются. Этот форматтер представляет вывод в стиле, похожем на документацию. Вам может быть интересно, как выглядят эти параметры, когда у вас есть сбой в тесте (Пример). Давайте изменим код в formatter_spec.rb, чтобы он выглядел так:
RSpec.describe "A spec file to demonstrate how RSpec Formatters work" do context 'when running some tests' do it 'the test usually calls the expect() method at least once' do expect(1 + 1).to eq(1) end end end
Ожидаемое ожидание (1 + 1). Уравнение (1) должно потерпеть неудачу. Сохраните свои изменения и повторно запустите вышеупомянутые команды —
rspec — отформатируйте прогресс formatter_spec.rb и запомните, поскольку форматер «progress» используется по умолчанию, вы можете просто запустить: rspec formatter_spec.rb . Вы должны увидеть этот вывод —
F Failures: 1) A spec file to demonstrate how RSpec Formatters work when running some tests the test usually calls the expect() method at least once Failure/Error: expect(1 + 1).to eq(1) expected: 1 got: 2 (compared using ==) # ./formatter_spec.rb:4:in `block (3 levels) in <top (required)>' Finished in 0.016 seconds (files took 0.11201 seconds to load) 1 example, 1 failure Failed examples: rspec ./formatter_spec.rb:3 # A spec file to demonstrate how RSpec Formatters work when running some tests the test usually calls the expect() method at least once
Теперь давайте попробуем doc formatter, запустим эту команду —
rspec --format doc formatter_spec.rb
Теперь, с неудачным тестом, вы должны увидеть этот вывод —
A spec file to demonstrate how RSpec Formatters work when running some tests the test usually calls the expect() method at least once (FAILED - 1) Failures: 1) A spec file to demonstrate how RSpec Formatters work when running some tests the test usually calls the expect() method at least once Failure/Error: expect(1 + 1).to eq(1) expected: 1 got: 2 (compared using ==) # ./formatter_spec.rb:4:in `block (3 levels) in <top (required)>' Finished in 0.015 seconds (files took 0.11401 seconds to load) 1 example, 1 failure
Неудачные примеры
rspec ./formatter_spec.rb:3 # Файл спецификаций, демонстрирующий работу RSpec Formatters при выполнении некоторых тестов, тест обычно вызывает метод hope () хотя бы один раз.
Форматеры RSpec предоставляют возможность изменить способ отображения результатов теста, даже возможно создать собственный форматтер, но это более сложная тема.
RSpec — ожидания
Когда вы изучаете RSpec, вы можете много читать об ожиданиях, и это может сначала немного запутать. Есть две основные детали, которые вы должны иметь в виду, когда видите термин «ожидание»:
-
Ожидание — это просто оператор в блоке it, который использует метод wait () . Вот и все. Это не сложнее, чем это. Когда у вас есть код, подобный этому: ожидайте (1 + 1). К уравнению (2) , у вас есть ожидание в вашем примере. Вы ожидаете, что выражение 1 + 1 оценивается как 2 . Хотя формулировка важна, поскольку RSpec является тестовой средой BDD. Называя это утверждение ожиданием, становится ясно, что ваш код RSpec описывает «поведение» кода, который он тестирует. Идея состоит в том, что вы выражаете, как должен вести себя код таким образом, который читается как документация.
-
Синтаксис ожидания является относительно новым. До того, как был введен метод wait () (еще в 2012 году), RSpec использовал другой синтаксис, основанный на методе must () . Вышеуказанное ожидание записывается так в старом синтаксисе: (1 + 1) .should eq (2) .
Ожидание — это просто оператор в блоке it, который использует метод wait () . Вот и все. Это не сложнее, чем это. Когда у вас есть код, подобный этому: ожидайте (1 + 1). К уравнению (2) , у вас есть ожидание в вашем примере. Вы ожидаете, что выражение 1 + 1 оценивается как 2 . Хотя формулировка важна, поскольку RSpec является тестовой средой BDD. Называя это утверждение ожиданием, становится ясно, что ваш код RSpec описывает «поведение» кода, который он тестирует. Идея состоит в том, что вы выражаете, как должен вести себя код таким образом, который читается как документация.
Синтаксис ожидания является относительно новым. До того, как был введен метод wait () (еще в 2012 году), RSpec использовал другой синтаксис, основанный на методе must () . Вышеуказанное ожидание записывается так в старом синтаксисе: (1 + 1) .should eq (2) .
Вы можете столкнуться со старым синтаксисом RSpec для Ожиданий при работе со старым кодом или более старой версией RSpec. Если вы используете старый синтаксис с новой версией RSpec, вы увидите предупреждение.
Например, с этим кодом —
RSpec.describe "An RSpec file that uses the old syntax" do it 'you should see a warning when you run this Example' do (1 + 1).should eq(2) end end
Когда вы запустите его, вы получите вывод, который выглядит следующим образом —
. Deprecation Warnings: Using `should` from rspec-expectations' old `:should` syntax without explicitly enabling the syntax is deprecated. Use the new `:expect` syntax or explicitly enable `:should` with `config.expect_with( :rspec) { |c| c.syntax = :should }` instead. Called from C:/rspec_tutorial/spec/old_expectation.rb:3 :in `block (2 levels) in <top (required)>'. If you need more of the backtrace for any of these deprecations to identify where to make the necessary changes, you can configure `config.raise_errors_for_deprecations!`, and it will turn the deprecation warnings into errors, giving you the full backtrace. 1 deprecation warning total Finished in 0.001 seconds (files took 0.11201 seconds to load) 1 example, 0 failures
Если от вас не требуется использовать старый синтаксис, настоятельно рекомендуется использовать функцию ожидаем () вместо необходимости ().