测试
使用 wippy/test 框架为您的 Lua 代码编写和运行测试 — 这是一个 BDD 风格的运行器,提供断言、生命周期钩子和模拟功能,通过 wippy run test 命令执行。
您将构建什么
Section titled “您将构建什么”一个小型库和覆盖它的测试套件:
- 一个包含
add和div函数的calc库。 - 一个测试入口,用于描述用例、断言行为并跳过一个待定用例。
- 通过
wippy run test进行一次全绿的测试运行。
-
一个 Wippy 项目(克隆 app-template,或在空目录中执行
wippy init)。 -
已安装测试框架和终端宿主:
Terminal window wippy add wippy/testwippy add wippy/terminalwippy install运行器会渲染一个实时的终端 UI,因此
wippy/terminal必须与wippy/test一同安装。
-- src/calc.lualocal function add(a, b) return a + bend
local function div(a, b) if b == 0 then return nil, "division by zero" end return a / bend
return { add = add, div = div }测试是一个普通的 function.lua 入口,标记了 meta.type: test。它的方法返回 test.run_cases(...) 产生的值,由运行器调用:
-- src/calc_test.lualocal test = require("test")local calc = require("calc")
local function define_tests() test.describe("calculator", function() local started = false
test.before_all(function() started = true end)
test.it("setup ran", function() test.is_true(started) end)
test.it("adds numbers", function() test.eq(calc.add(2, 3), 5) end)
test.it("returns error on divide by zero", function() local result, err = calc.div(1, 0) test.has_error(result, err) test.contains(err, "division by zero") end)
test.it_skip("not implemented yet", function() test.fail("should not run") end) end)end
return { run = test.run_cases(define_tests) }注册这两个入口。发现机制依据 meta.type: test;meta.suite 在输出中将结果分组:
version: "1.0"namespace: app
entries: - name: calc kind: library.lua source: file://calc.lua
- name: calc_test kind: function.lua meta: name: Calculator Test type: test suite: calculator source: file://calc_test.lua method: run imports: test: wippy.test:test calc: app:calcimports 映射控制测试内部 require(...) 解析到的内容:test 绑定框架,calc 绑定被测单元。
wippy run test在迭代时过滤到单个套件(匹配入口 id 或套件名称):
wippy run test calculator上述套件的输出:
calculator (4) 3/4 1 skipped 1ms o setup ran o adds numbers o returns error on divide by zero - not implemented yet (skipped)
PASSED 3 tests 1 skipped 1ms当每个用例都通过时,wippy run test 退出码为 0,任何失败时为 1,因此可以直接接入 CI。
每个断言在失败时抛出错误;类型守卫还会返回经过验证的值。
| 断言 | 检查 |
|---|---|
test.eq(a, b) / test.neq(a, b) | 相等 / 不相等 |
test.ok(v) / test.fail(msg) | 真值 / 强制失败 |
test.is_nil(v) / test.not_nil(v) | Nil / 非 nil |
test.is_true(v) / test.is_false(v) | 布尔值 |
test.is_string/number/table/function/boolean(v) | 类型守卫(返回 v) |
test.contains(str, sub) / test.matches(str, pattern) | 子串 / Lua 模式 |
test.has_key(tbl, key) / test.len(v, n) | 映射键 / 长度 |
test.gt/gte/lt/lte(a, b) | 数值比较 |
test.throws(fn) / test.has_error(val, err) / test.no_error(val, err) | 错误处理 |
它们都接受一个可选的尾随消息参数。
生命周期与模拟
Section titled “生命周期与模拟”在 describe 块内调用这些:
test.before_all/test.after_all— 每个块运行一次。test.before_each/test.after_each— 围绕每个用例运行。test.mock("module.field", fn)— 为当前用例替换一个函数;模拟在每个用例后自动恢复。使用test.restore_all_mocks()可提前清除它们。
嵌套的 describe 块继承父级钩子(外层 before_* 优先,内层 after_* 优先)。
- Hello World — 最小化的项目布局
- 入口种类 —
function.lua、library.lua及其相关类型 - 测试框架 — 运行器和事件协议的完整参考