Пакет PonyTest
Пакет PonyTest предоставляет фреймворк для модульного тестирования. Он разработан для максимальной простоты использования как для автора модульного теста, так и для пользователя, запускающего тесты.
Для упрощения написания и распространения тестов этот пакет зависит от как можно меньшего числа других пакетов. В настоящее время требуемые пакеты:
- builtin
- time
- collections
Каждый модульный тест представляет собой класс с единственной тестовой функцией. По умолчанию все тесты выполняются параллельно.
Каждый запуск теста получает объект-помощник. Он предоставляет функции ведения журнала и проверки утверждений. По умолчанию сообщения журнала отображаются только для тестов, которые завершились неудачно.
При неудаче любой функции проверки утверждений тест считается проваленным. Однако тесты также могут указывать на неудачу, выбросив исключение в тестовой функции.
Пример программы
Для использования PonyTest просто напишите класс для каждого теста и тип TestList, который сообщает объекту PonyTest о тесте. Как правило, TestList будет Main для пакета.
Следующая программа полностью демонстрирует 2 тривиальных теста.
use "ponytest"
actor Main is TestList
new create(env: Env) =>
PonyTest(env, this)
new make() =>
None
fun tag tests(test: PonyTest) =>
test(_TestAdd)
test(_TestSub)
class iso _TestAdd is UnitTest
fun name():String => "addition"
fun apply(h: TestHelper) =>
h.assert_eq[U32](4, 2 + 2)
class iso _TestSub is UnitTest
fun name():String => "subtraction"
fun apply(h: TestHelper) =>
h.assert_eq[U32](2, 4 - 2)
Конструктор make() не нужен для этого примера. Однако он позволяет легко агрегировать тесты (см. ниже), поэтому рекомендуется, чтобы все тестовые Main предоставляли его.
Main.create() вызывается только при запуске программы в текущем пакете. Main.make() вызывается во время агрегирования. При необходимости дополнительный код может быть добавлен в любой из этих конструкторов для выполнения дополнительных задач.
Имена тестов
Тесты идентифицируются именами, которые используются при печати результатов тестов и в командной строке для выбора тестов, которые необходимо запустить. Эти имена независимы от имен тестовых классов в исходном коде Pony.
Для этих имен могут использоваться произвольные строки, но для больших проектов настоятельно рекомендуется использовать иерархическую схему именования, чтобы упростить выбор групп тестов.
Вы можете пропустить любые тесты, имена которых начинаются с заданной строки, используя опцию командной строки --exclude=[prefix].
Вы можете запустить только тесты, имена которых начинаются с заданной строки, используя опцию командной строки --only=[prefix].
Агрегирование
Часто желательно запускать набор модульных тестов из нескольких разных файлов исходного кода. Например, если несколько пакетов в сборке имеют свои собственные модульные тесты, может быть полезно запустить все тесты сборки вместе.
Этого можно достичь, написав класс агрегированного списка тестов, который вызывает функцию списка для каждого пакета. Следующий пример агрегирует тесты из пакетов foo и bar.
use "ponytest"
use foo = "foo"
use bar = "bar"
actor Main is TestList
new create(env: Env) =>
PonyTest(env, this)
new make() =>
None
fun tag tests(test: PonyTest) =>
foo.Main.make().tests(test)
bar.Main.make().tests(test)
Классы агрегированных тестов могут быть сами агрегированы. Каждый класс списка тестов может содержать любую комбинацию собственных тестов и агрегированных списков.
Длительные тесты
Простые тесты выполняются в пределах одной функции. Когда эта функция завершается, возвращая значение или вызывая ошибку, тест завершается. Это нецелесообразно для тестов, которым требуется использовать акторы.
Длительные тесты позволяют отложить завершение. Любой тест может вызвать long_test() в своем TestHelper, чтобы указать, что ему нужно продолжать работу. Когда тест, наконец, завершается, он вызывает complete() в своем TestHelper.
Функция complete() принимает параметр Bool, чтобы указать, был ли тест успешным. Если какие-либо проверки утверждений завершаются неудачно, тест будет считаться неудачным независимо от значения этого параметра. Однако complete() все равно необходимо вызвать.
Поскольку тесты с ошибками могут зависать, для каждого длительного теста должен быть указан таймаут. Когда функция тестовой функции завершается, запускается таймер с указанным таймаутом. Если этот таймер сработает до вызова complete(), тест отмечается как неудачный, и таймаут сообщается.
При таймауте вызывается функция timed_out() в объекте модульного теста. Это должно выполнить все необходимые задачи по очистке, специфичные для теста, чтобы программа могла выйти. Нет необходимости вызывать complete(), если произошел таймаут, хотя это не ошибка.
Обратите внимание, что таймаут имеет значение только в том случае, если тест зависает и в противном случае не позволяет программе тестирования завершиться. Установка очень большого таймаута для тестов, которые не должны зависать, вполне приемлема и не заставит тест занимать больше времени, если он успешно выполняется.
Таймауты не следует использовать в качестве стандартного метода определения того, завершился ли тест неудачно.
Группы исключения
По умолчанию все тесты выполняются параллельно. Это может быть проблемой для некоторых тестов, например, если они изменяют внешний файл или используют системный ресурс. Чтобы исправить эту проблему, любое количество тестов может быть помещено в группу исключения.
Никакие тесты, которые находятся в одной группе исключения, не будут выполняться параллельно.
Группы исключений идентифицируются по имени, могут использоваться произвольные строки. Можно использовать несколько групп исключения, и тесты в разных группах могут выполняться параллельно. Тесты, которые не указывают группу исключения, могут выполняться параллельно с другими тестами.
Опция командной строки "--sequential" предотвращает одновременное выполнение каких-либо тестов, независимо от групп исключений. Это предназначено для отладки, а не для стандартного использования.
Метки
Тесты могут иметь метки. Метки используются для фильтрации запускаемых тестов путем установки аргумента командной строки --label=[some custom label]. Это можно использовать для разделения модульных тестов и интеграционных тестов.
По умолчанию метка пустая. Вы можете установить ее, переопределив метод label(): String в модульном тесте.
use "ponytest"
class iso _I8AddTest is UnitTest
fun name(): String => "_I8AddTest"
fun label(): String => "simple"
fun apply(h: TestHelper) =>
h.assert_eq[I8](1, 1)
Настройка и разборка тестовой среды
Настройка
Любой вид фикстуры или среды, необходимой для выполнения UnitTest, может быть настроен либо в конструкторе тестов, либо в функции, называемой set_up().
set_up() вызывается перед выполнением теста. Она частичная, если возникает ошибка, тест не выполняется, но регистрируется как завершившийся неудачно во время настройки. Тестовый TestHelper передается set_up() для записи сообщений или доступа к тестам Env через TestHelper.env.
Разборка
Каждый объект модульного теста может определить функцию tear_down(). Она вызывается после завершения теста, чтобы разрешить разборку любой сложной среды, которая должна была быть настроена для теста.
Функция tear_down() вызывается для каждого теста независимо от того, пройден он или нет. Если тест превышает лимит времени, tear_down() будет вызвана после возвращения timed_out().
Когда тест находится в группе исключений, вызов tear_down() считается частью запускаемых тестов. Следующий тест в группе исключений не начнется до тех пор, пока tear_down() не вернет значение для текущего теста.
Тестовый TestHelper передается функции tear_down(), и разрешается записывать сообщения и вызывать функции утверждений во время разборки.
Пример
В следующем примере временная директория создается в функции set_up() и удаляется в функции tear_down(), тем самым упрощая саму тестовую функцию:
use "ponytest"
use "files"
class iso TempDirTest
var tmp_dir: (FilePath | None) = None
fun name(): String => "temp-dir"
fun ref set_up(h: TestHelper)? =>
tmp_dir = FilePath.mkdtemp(h.env.root as AmbientAuth, "temp-dir")?
fun ref tear_down(h: TestHelper) =>
try
(tmp_dir as FilePath).remove()
end
fun apply(h: TestHelper)? =>
let dir = tmp_dir as FilePath
// do something inside the temporary directory
Открытые типы
© 2016-2020, The Pony Developers
© 2014-2015, Causality Ltd.
Licensed under the BSD 2-Clause License.
https://stdlib.ponylang.io/ponytest--index