Lisp 测试:fiveam 框架
安装 fiveam 测试框架。在 Common Lisp 环境中(如 SBCL 或 CCL),通过 Quicklisp 加载:
(ql:quickload "fiveam")
若尚未配置 Quicklisp,先访问 https://www.quicklisp.org/ 下载并运行安装脚本,再执行上述命令。
定义测试套件。使用 def-suite 宏创建一个命名的测试容器,用于组织相关测试用例:
(def-suite my-tests
:description "基础功能测试套件")
此命令声明名为 my-tests 的测试套件,后续所有测试将归属其中。
注册当前套件为默认上下文。调用 in-suite 宏,使后续定义的测试自动加入该套件:
(in-suite my-tests)
此后无需在每个测试中重复指定套件名称。
编写单个测试用例。使用 test 宏定义具体断言,名称需具描述性:
(test addition-works
(is (= (+ 2 3) 5)))
此处 is 是核心断言宏,验证表达式结果是否为真。若 (+ 2 3) 不等于 5,测试失败。
组合多个断言于同一测试。在单个 test 块内可包含任意数量的 is 断言:
(test string-operations
(is (string= (string-upcase "hello") "HELLO"))
(is (string= (subseq "abcdef" 1 4) "bcd")))
所有断言必须通过,该测试才算成功。
处理预期失败的测试。当验证某操作应抛出错误时,使用 signals 断言:
(test division-by-zero-errors
(signals division-by-zero (floor 10 0)))
signals 接受错误类型和待执行表达式,仅当指定错误被抛出时测试通过。
跳过暂时无法运行的测试。在 test 宏中添加 :skip t 参数:
(test unfinished-feature
:skip t
(is (= (* 7 8) 56)))
测试运行器会标记该用例为“跳过”,不影响整体结果。
运行全部测试。调用 run! 函数并传入套件名称:
(run! 'my-tests)
终端将输出每个测试的状态(通过、失败或跳过)及详细错误信息(如有)。
运行单个测试用例。直接向 run! 传递测试名称符号:
(run! 'addition-works)
适用于快速验证特定功能,无需执行整个套件。
检查测试结果结构。run! 返回 fiveam::test-result 对象,可通过以下函数提取信息:
(let ((result (run! 'my-tests)))
(format t "通过数: ~A~%" (fiveam::passed-count result))
(format t "失败数: ~A~%" (fiveam::failed-count result))
(format t "跳过数: ~A~%" (fiveam::skipped-count result)))
这些访问器函数帮助自动化分析测试报告。
组织嵌套测试套件。通过 :in 参数将子套件挂载到父套件下:
(def-suite math-tests)
(def-suite arithmetic-tests :in math-tests)
(def-suite geometry-tests :in math-tests)
(in-suite arithmetic-tests)
(test add-test (is (= (+ 1 1) 2)))
(in-suite geometry-tests)
(test circle-area (is (= (* pi 4) (* pi (expt 2 2)))))
运行 math-tests 时会递归执行所有子套件中的测试。
自定义断言逻辑。当内置 is 无法满足需求时,直接使用 Common Lisp 条件判断配合 fail 宏:
(test custom-check
(let ((value (get-config :timeout)))
(unless (and (numberp value) (> value 0))
(fail "超时配置必须为正数,实际值: ~A" value))))
fail 接受格式字符串和参数,立即终止当前测试并标记为失败。
调试失败测试。在测试代码中插入临时输出以观察中间状态:
(test debug-example
(let ((x (complex-calculation)))
(format *debug-io* "计算中间值: ~A~%" x)
(is (> x 100))))
使用 *debug-io* 而非 *standard-output*,确保调试信息不干扰测试报告。
集成到项目构建流程。在系统定义文件(.asd)中添加测试依赖:
(asdf:defsystem #:my-project-tests
:depends-on (#:my-project #:fiveam)
:components ((:file "tests"))
:perform (asdf:test-op (op c) (symbol-call :fiveam '#:run! '#:my-tests)))
此后可通过 (asdf:test-system :my-project) 自动运行测试。
清理测试副作用。使用 finishes 确保测试代码完整执行,或结合 unwind-protect 恢复环境:
(test file-test
(let ((temp-file (make-temp-file)))
(unwind-protect
(progn
(write-data-to temp-file)
(is (data-valid-p (read-data-from temp-file))))
(delete-file temp-file))))
unwind-protect 保证无论测试成败,临时文件都会被删除。
比较浮点数结果。避免直接使用 =,改用 is-approx 处理精度误差:
(test float-comparison
(is-approx (sin (/ pi 2)) 1.0 :epsilon 1e-10))
:epsilon 参数指定允许的最大误差范围,默认值为 1e-5。
生成测试覆盖率报告。配合 cl-coveralls 工具,在加载 fiveam 前启用代码覆盖追踪:
(ql:quickload '(:fiveam :cl-coveralls))
(cl-coveralls:with-coveralls (:exclude ("tests.lisp"))
(run! 'my-tests))
运行结束后自动上传覆盖数据至 Coveralls 服务。
模拟外部依赖。使用 mockingbird 库替换函数行为:
(ql:quickload "mockingbird")
(test api-call-test
(with-mocks ()
(mock http-get :returning "{'status': 'ok'}")
(is (string= (fetch-status) "ok"))))
mock 宏临时重定义函数返回值,隔离网络等不稳定因素。
并行运行测试套件。通过 fiveam 的扩展库 parachute 实现并发执行:
(ql:quickload "parachute/fiveam")
(parachute:run 'my-tests :parallel t)
注意:仅当测试间无共享状态时才安全启用并行模式。
导出测试结果为 TAP 格式。供 CI 系统解析:
(fiveam:explain! 'my-tests :reporter 'fiveam:tap-reporter)
输出符合 Test Anything Protocol 标准的文本流,兼容 Jenkins、GitHub Actions 等平台。
重用测试夹具(fixture)。定义宏封装常用初始化逻辑:
(defmacro with-db-connection (&body body)
`(let ((conn (connect-to-test-db)))
(unwind-protect
(progn ,@body)
(disconnect conn))))
(test db-query-test
(with-db-connection
(is (= (count-records conn "users") 0))))
宏确保每次测试获得干净的数据库连接,并在结束时正确释放资源。

暂无评论,快来抢沙发吧!