Фреймворк тестирования
Фреймворк тестирования
Заголовок раздела «Фреймворк тестирования»Модуль wippy/test предоставляет BDD-фреймворк для тестирования с утверждениями, хуками жизненного цикла и моками.
Настройка
Заголовок раздела «Настройка»Добавьте зависимость:
wippy add wippy/testwippy installМодуль автоматически регистрирует команду test. После установки wippy run test обнаруживает и запускает все тестовые записи в проекте.
Определение тестов
Заголовок раздела «Определение тестов»Тесты — это записи function.lua с meta.type: test:
version: "1.0"namespace: app.test
entries: - name: math kind: function.lua meta: type: test suite: math name: Математические операции source: file://math_test.lua method: run imports: test: wippy.test:testМетаданные теста
Заголовок раздела «Метаданные теста»| Field | Required | Описание |
|---|---|---|
type | Yes | Должно быть "test", чтобы раннер обнаружил запись |
suite | No | Группирует тесты в выводе раннера |
name | No | Отображаемое имя в выводе раннера |
order | No | Порядок сортировки внутри набора (меньшие значения выполняются первыми) |
Написание тестов
Заголовок раздела «Написание тестов»BDD-стиль
Заголовок раздела «BDD-стиль»Используйте блоки describe и it для структурирования тестов:
local test = require("test")
local function define_tests() test.describe("calculator", function() test.it("adds numbers", function() test.eq(1 + 1, 2) end)
test.it("multiplies numbers", function() test.eq(3 * 4, 12) end) end)end
local run_cases = test.run_cases(define_tests)
local function run(options) local result = run_cases(options) if result.failed_tests > 0 then error("tests failed: " .. result.failed_tests) end return resultend
return { run = run }Вложенные наборы
Заголовок раздела «Вложенные наборы»Наборы тестов можно вкладывать для организации:
test.describe("user", function() test.describe("validation", function() test.it("requires name", function() test.ok(validate({}).error) end)
test.it("accepts valid input", function() test.is_nil(validate({name = "Alice"}).error) end) end)
test.describe("formatting", function() test.it("formats display name", function() test.eq(format_name("alice"), "Alice") end) end)end)Пропуск тестов
Заголовок раздела «Пропуск тестов»test.it_skip("not implemented yet", function() test.fail("TODO")end)Пропущенные тесты отображаются в выводе, но не считаются провалами.
Псевдонимы наборов
Заголовок раздела «Псевдонимы наборов»test.spec и test.context являются псевдонимами для test.describe:
test.spec("feature", function() test.context("when valid input", function() test.it("succeeds", function() test.ok(true) end) end)end)Утверждения
Заголовок раздела «Утверждения»Равенство
Заголовок раздела «Равенство»test.eq(actual, expected, msg?) -- actual == expectedtest.neq(actual, expected, msg?) -- actual ~= expectedИстинность
Заголовок раздела «Истинность»test.ok(val, msg?) -- val is truthytest.fail(msg?) -- unconditional failureПроверка на nil
Заголовок раздела «Проверка на nil»test.is_nil(val, msg?) -- val == niltest.not_nil(val, msg?) -- val ~= nilПроверка типов
Заголовок раздела «Проверка типов»test.is_true(val, msg?) -- val == truetest.is_false(val, msg?) -- val == falsetest.is_string(val, msg?)test.is_number(val, msg?)test.is_table(val, msg?)test.is_function(val, msg?)test.is_boolean(val, msg?)Строки и коллекции
Заголовок раздела «Строки и коллекции»test.contains(str, substr, msg?) -- substring matchtest.matches(str, pattern, msg?) -- Lua pattern matchtest.has_key(tbl, key, msg?) -- table key existstest.len(val, expected, msg?) -- #val == expectedЧисловые сравнения
Заголовок раздела «Числовые сравнения»test.gt(a, b, msg?) -- a > btest.gte(a, b, msg?) -- a >= btest.lt(a, b, msg?) -- a < btest.lte(a, b, msg?) -- a <= bОбработка ошибок
Заголовок раздела «Обработка ошибок»test.throws(fn, msg?) -- fn() raises error, returns ittest.has_error(val, err, msg?) -- val is nil, err is not niltest.no_error(val, err, msg?) -- err is nilВсе утверждения принимают необязательное сообщение в качестве последнего аргумента. При провале сообщение включается в вывод ошибки.
Хуки жизненного цикла
Заголовок раздела «Хуки жизненного цикла»test.describe("database", function() test.before_all(function() -- runs once before the suite db = connect() end)
test.after_all(function() -- runs once after the suite db:close() end)
test.before_each(function() -- runs before each test db:begin_transaction() end)
test.after_each(function() -- runs after each test db:rollback() end)
test.it("inserts a record", function() db:exec("INSERT INTO users (name) VALUES ('Alice')") local count = db:query_row("SELECT COUNT(*) FROM users") test.eq(count, 1) end)end)Хуки во вложенных наборах выполняются по порядку: родительский before_each выполняется перед дочерним before_each, а дочерний after_each выполняется перед родительским after_each.
Система моков заменяет поля глобальных объектов и автоматически восстанавливает их после каждого теста.
Базовые моки
Заголовок раздела «Базовые моки»test.describe("notifications", function() test.it("sends message", function() local sent = false test.mock("process.send", function(pid, topic, payload) sent = true end)
notify_user("hello") test.is_true(sent) -- mock is auto-restored after this test end)end)API моков
Заголовок раздела «API моков»test.mock("object.field", replacement) -- replace a global fieldtest.mock_process("field", replacement) -- shorthand for process fieldstest.restore_mock("object.field") -- restore one mocktest.restore_all_mocks() -- restore all mocksПути моков используют точечную нотацию: "process.send" заменяет _G.process.send.
Моки для process.send автоматически проксируют сообщения тестового фреймворка через оригинальную функцию, чтобы отчетность о событиях тестов продолжала работать при замоканном process.send.
Все моки автоматически восстанавливаются после каждого теста через хук after_each.
Запуск тестов
Заголовок раздела «Запуск тестов»Запуск всех тестов
Заголовок раздела «Запуск всех тестов»wippy run testФильтрация по шаблону
Заголовок раздела «Фильтрация по шаблону»wippy run test mathwippy run test user validationФильтры сопоставляются с ID записей. Несколько шаблонов комбинируются.
Пример вывода
Заголовок раздела «Пример вывода»3 tests in 1 suites
calculator + adds numbers 0ms + multiplies numbers 0ms - divides by zero 1ms Error: expected error, got nil
1 suite | 2 passed | 1 failed | 0 skipped | 3msПростые тесты
Заголовок раздела «Простые тесты»Для тестов, которым не нужен BDD-фреймворк, определите простую функцию, которая возвращает true или вызывает ошибку:
local funcs = require("funcs")
local function main() local result, err = funcs.call("app:my_function", "input") if err then error("call failed: " .. tostring(err)) end if result ~= "expected" then error("expected 'expected', got: " .. tostring(result)) end return trueend
return { main = main } - name: integration kind: function.lua meta: type: test suite: integration source: file://integration_test.lua method: main modules: - funcsРаннер определяет, использует ли тест BDD-события или возвращает простое значение. Оба подхода работают с wippy run test.
Структура проекта
Заголовок раздела «Структура проекта»Типичная структура тестов:
src/ _index.yaml app.lua test/ _index.yaml # test entries math_test.lua user_test.lua integration_test.luaТестовый _index.yaml определяет пространство имен и записи тестов:
version: "1.0"namespace: app.test
entries: - name: math kind: function.lua meta: type: test suite: math source: file://math_test.lua method: run imports: test: wippy.test:test
- name: user kind: function.lua meta: type: test suite: user source: file://user_test.lua method: run imports: test: wippy.test:testТребования к инфраструктуре
Заголовок раздела «Требования к инфраструктуре»Для работы раннера тестов необходимы process.host и terminal.host в вашем приложении. Обычно они уже присутствуют. Если нет, добавьте их:
entries: - name: processes kind: process.host lifecycle: auto_start: true
- name: terminal kind: terminal.host lifecycle: auto_start: trueСм. также
Заголовок раздела «См. также»- Обзор фреймворка - Использование модулей фреймворка
- CLI-справочник - CLI-команды
- Функции - Реестр функций