Тестирование кода на Crystal
Crystal поставляется с полнофункциональной библиотекой спецификаций в модуле Spec. Она предоставляет структуру для написания исполняемых примеров того, как должен работать ваш код.
Созданная под влиянием Rspec, она включает предметно-ориентированный язык (DSL), позволяющий писать примеры в стиле, близком к обычному английскому.
Базовая спецификация выглядит примерно так:
require "spec"
describe Array do
describe "#size" do
it "correctly reports the number of elements in the Array" do
[1, 2, 3].size.should eq 3
end
end
describe "#empty?" do
it "is true when no elements are in the array" do
([] of Int32).empty?.should be_true
end
it "is false if there are elements in the array" do
[1].empty?.should be_false
end
end
end
Структура файла спецификаций
Чтобы использовать модуль спецификаций и DSL, необходимо добавить require "spec" в файлы спецификаций. Во многих проектах используется специальный вспомогательный файл спецификаций, в котором собраны эти директивы include.
Конкретные тестовые случаи определяются в блоках it. Необязательная (но настоятельно рекомендуемая) описательная строка указывает цель теста, а блок содержит основную логику его выполнения.
Тестовые случаи, которые определены или намечены, но ещё не должны работать, можно определить с помощью pending вместо it. Они не будут запущены, но появятся в отчёте о спецификациях как ожидающие выполнения.
Блок it содержит пример, который должен вызвать проверяемый код и определить ожидаемый результат. Каждый пример может содержать несколько проверок, но он должен проверять только одно конкретное поведение.
Когда подключён spec, у каждого объекта есть методы экземпляра #should и #should_not. Эти методы вызываются для проверяемого значения с ожидаемым значением в качестве аргумента. Если ожидание выполняется, выполнение кода продолжается. В противном случае пример считается неудачным, и остальной код в этом блоке не выполняется.
В тестовых файлах спецификации организованы в группы примеров, определяемые разделами describe и context. Обычно внешний блок describe задаёт основную единицу (например, класс), которую проверяет спецификация. Внутри этой единицы могут быть вложены дополнительные разделы describe, задающие более мелкие проверяемые единицы (например, отдельные методы).
Для модульных тестов рекомендуется придерживаться соглашений об именах методов: внешний describe — это имя класса, внутренний describe задаёт методы. Методы экземпляра имеют префикс #, а методы класса — ..
Чтобы задать определённый контекст — например, пустой массив или массив с элементами, — можно использовать метод context. Он помогает донести контекст до читателя. У него другое имя, но работает он точно так же, как describe.
describe и context принимают в качестве аргумента описание (обычно строку) и блок, содержащий отдельные спецификации или вложенные группы.
Ожидания
Ожидания определяют, соответствует ли проверяемое значение (фактическое) заданному значению или определённым критериям.
Эквивалентность, идентичность и тип
Существуют методы для создания ожиданий, проверяющих эквивалентность (eq), идентичность (be), тип (be_a) и nil (be_nil). Обратите внимание, что ожидание идентичности использует .same?, который проверяет, идентичны ли #object_id. Это верно, только если ожидаемое значение указывает на тот же объект, а не на эквивалентный объект. Это возможно только для ссылочных типов и не работает для типов-значений, таких как структуры или числа.
actual.should eq(expected) # passes if actual == expected
actual.should be(expected) # passes if actual.same?(expected)
actual.should be_a(expected) # passes if actual.is_a?(expected)
actual.should be_nil # passes if actual.nil?
Истинность
actual.should be_true # passes if actual == true
actual.should be_false # passes if actual == false
actual.should be_truthy # passes if actual is truthy (neither nil nor false nor Pointer.null)
actual.should be_falsey # passes if actual is falsey (nil, false or Pointer.null)
Сравнения
actual.should be < expected # passes if actual < expected
actual.should be <= expected # passes if actual <= expected
actual.should be > expected # passes if actual > expected
actual.should be >= expected # passes if actual >= expected
Другие сопоставители
actual.should be_close(expected, delta) # passes if actual is within delta of expected:
# (actual - expected).abs <= delta
actual.should contain(expected) # passes if actual.includes?(expected)
actual.should match(expected) # passes if actual =~ expected
Ожидание ошибок
Эти сопоставители выполняют блок и завершаются успешно, если он вызывает определённое исключение.
expect_raises(MyError) do
# Passes if this block raises an exception of type MyError.
end
expect_raises(MyError, "error message") do
# Passes if this block raises an exception of type MyError
# and the error message contains "error message".
end
expect_raises(MyError, /error \w{7}/) do
# Passes if this block raises an exception of type MyError
# and the error message matches the regular expression.
end
Они возвращают перехваченное исключение, чтобы его можно было использовать для дальнейших проверок, например для подтверждения определённых свойств исключения.
Фокусировка на группе спецификаций
Блоки describe, context и it можно пометить с помощью focus: true, например так:
it "adds", focus: true do
(2 + 2).should_not eq(5)
end
Если что-либо из этого помечено с помощью focus: true, будут запущены только эти примеры.
Добавление тегов к спецификациям
Теги позволяют группировать спецификации и запускать только их часть, передав аргумент --tag средству запуска спецификаций (см. раздел Использование компилятора).
Блокам describe, context и it можно назначать теги, например так:
it "is slow", tags: "slow" do
sleep 60
true.should be(true)
end
it "is fast", tags: "fast" do
true.should be(true)
end
Тег, назначенный группе примеров (describe или context), распространяется на все содержащиеся в ней примеры.
Можно указать несколько тегов, передав Enumerable, например Array или Set.
Запуск спецификаций
В компиляторе Crystal есть команда spec с инструментами для ограничения набора запускаемых примеров и настройки вывода. Все спецификации проекта компилируются и выполняются командой crystal spec.
По соглашению спецификации хранятся в каталоге spec/ проекта. Чтобы команда компилятора распознала файлы спецификаций, их имена должны оканчиваться на _spec.cr.
Можно компилировать и запускать спецификации из дерева каталогов, отдельных файлов или определённых строк файла. Если указанная строка является началом раздела describe или context, запускаются все спецификации внутри этой группы.
Форматтер по умолчанию выводит для неудачных спецификаций команду в формате «файл и строка», что позволяет легко повторно запустить только эту конкретную спецификацию.
Отключить цвета можно с помощью параметра --no-color.
Случайный порядок запуска спецификаций
По умолчанию спецификации запускаются в заданном порядке, но их можно запустить в случайном порядке, передав --order random команде crystal spec.
После завершения спецификаций, запущенных в случайном порядке, будет показано начальное значение. Его можно использовать, чтобы повторно запустить спецификации в том же порядке, передав это значение команде --order.
Примеры
# Run all specs in files matching spec/**/*_spec.cr
crystal spec
# Run all specs in files matching spec/**/*_spec.cr without colors
crystal spec --no-color
# Run all specs in files matching spec/my/test/**/*_spec.cr
crystal spec spec/my/test/
# Run all specs in spec/my/test/file_spec.cr
crystal spec spec/my/test/file_spec.cr
# Run the spec or group defined in line 14 of spec/my/test/file_spec.cr
crystal spec spec/my/test/file_spec.cr:14
# Run all specs tagged with "fast"
crystal spec --tag 'fast'
# Run all specs not tagged with "slow"
crystal spec --tag '~slow'
Вспомогательный файл спецификаций
Во многих проектах используется специальный вспомогательный файл спецификаций, обычно называемый spec/spec_helper.cr.
Этот файл используется для подключения spec и других файлов, например кода проекта, необходимого для каждого файла спецификаций. Здесь также удобно определять глобальные вспомогательные методы, которые упрощают написание спецификаций и позволяют избежать дублирования кода.
spec/spec_helper.cr
require "spec"
require "../src/my_project.cr"
def create_test_object(name)
project = MyProject.new(option: false)
object = project.create_object(name)
object
end
spec/my_project_spec.cr
require "./spec_helper"
describe "MyProject::Object" do
it "is created" do
object = create_test_object(name)
object.should_not be_nil
end
end
To the extent possible under law, the persons who contributed to this workhave waived
all copyright and related or neighboring rights to this workby associating CC0 with it.
https://crystal-lang.org/reference/1.2/guides/testing.html