«Стандартный» способ автономного тестирования компонентов в Ruby — библиотека Test::UnitНатаниэля Тэлбота (Nathaniel Talbott). Она была включена в дистрибутив Ruby еще в 2001 году.
В этой библиотеке для анализа тестового кода применяется отражение. Когда вы создаете подкласс класса Test::Unit::TestCase, все методы, имена которых начинаются с test, считаются тестовыми.
require 'test/unit'
class TC_MyTest < Test::Unit::TestCase
def test_001
# ...
end
def test_002
# ...
end
# ...
end
Методы необязательно нумеровать, как показано в этом примере. Это мое личное соглашение, но, конечно, есть и другие.
Нежелательно и, пожалуй, даже неправильно составлять тесты так, чтобы их поведение зависело от порядка запуска. Однако Test::Unitпрогоняет их в алфавитном (лексикографическом) порядке, поэтому, нумеруя свои методы, я вижу, как они выполняются в определенной последовательности.
Я также предпочитаю включать некий «заголовок» в имя метода (описывающий его область действия или назначение):
def test_053_default_to_current_directory
# ...
end
def test_054_use_specified_directory
# ...
end
Кроме прочего, неплохо оставлять хотя бы однострочный комментарий, касающийся цели и смысла теста. Вообще говоря, у каждого теста должна быть только одна цель.
А если нужно организовать некую среду выполнения, для чего требуется время? Неразумно делать это для каждого теста, и мы не вправе завести для данной цели отдельный метод (поскольку поведение не должно зависеть от порядка прогона).
Если всем тестам нужна особая среда, можно воспользоваться методами класса setupи teardown. Возможно, вам это покажется странным, но вызываются они для каждого теста. Если вы хотите выполнить настройку один раз, перед прогоном одного конкретного или всех тестов, то можете поместить соответствующий код в тело класса раньше всех тестовых методов (или даже до самого класса).
А если после выполнения всех тестов нужно разрушить созданную среду? По техническим причинам (так уж работает библиотека Test::Unit) сделать это трудно. «Самый лучший» способ — переопределить метод run всего комплекта тестов (но не метод класса run), обернув его функциональность. Рассмотрим пример в листинге 16.1.
Листинг 16.1. Подготовка и разрушение среды исполнения
require 'test/unit'
class MyTest < Test::Unit::TestCase
def self.major_setup
# ...
end
def self.major_teardown
# ...
end
def self.suite
mysuite = super # Вызвать метод suite родителя.
def mysuite.run(*args) # Добавить синглетный метод
MyTest.major_setup
super
MyTest.major_teardown
end
mysuite # и вернуть новое значение.
end
def setup
# ...
end
def teardown
# ...
end
def test_001
# ...
end
def test_002
# ...
end
# ...
end
Вряд ли вы будете поступать так часто. О методе suiteмы поговорим чуть позже, а пока продолжим рассмотрение отдельных тестов.
Что должно входить в тест? Нужно как-то решить, прошел он или нет. Для этой цели применяются утверждения .
Простейшее утверждение — это метод assert. Он принимает проверяемый параметр и еще один необязательный параметр (сообщение). Если значение параметра истинно (то есть все, кроме falseи nil), тест прошел. В противном случае тест не прошел — тогда печатается сообщение, если оно было задано.
Есть и другие методы для формулирования утверждений. Обратите внимание, что «ожидаемое» значение всегда предшествует «фактическому».
assert_equal(expected, actual) # assert(expected==actual)
assert_not_equal(expected, actual) # assert(expected!=actual)
assert_match(regex, string) # assert(regex =~ string)
assert_no_match(regex, string) # assert(regex string)
assert_nil(object) # assert(object.nil?)
assert_not_nil(object) # assert(!object.nil?)
Некоторые утверждения носят более объектно-ориентированный характер:
assert_instance_of(klass, obj) # assert(obj.instance_of? klass)
assert_kind_of(klass, obj) # assert(obj.kind_of? klass)
assert_respond_to(obj, meth) # assert(obj.respond_to? meth)
Другие относятся к исключениям и символам, которые генерируются методом throw. Понятно, что такие методы принимают блок.
assert_nothing_thrown { ... } # Не было throw.
assert_nothing_raised { ... } # Не было raise.
assert_throws(symbol) { ... } # Символ в результате throw.
assert_raises(exception) { ... } # Исключение в результате raise.
Читать дальше
Конец ознакомительного отрывка
Купить книгу