文章
Clojure中怎么测试println输出内容
目录
在 Clojure 的单元测试中,我们通常使用 clojure.test 的 is 来验证函数返回值。然而,当函数中存在 副作用(如 println 输出)时,如何在测试中断言它的输出呢?
本文将介绍两种方法:
- 捕获标准输出(
with-out-str) - 临时重定义
println捕获参数(with-redefs)
1️⃣ 方法一:使用 with-out-str 捕获标准输出(推荐)#
with-out-str 会捕获代码块中所有打印到标准输出的内容,并返回字符串。
示例:
(deftest atom-watcher
(testing "use watcher"
(let [a (atom 0)
out (with-out-str ;; 捕获 println 输出
(add-watch a :print
#(println "Changed from" %3 "to" %4))
(swap! a + 2))] ;; 触发 watcher
;; 验证 atom 的值
(is (= 2 @a))
;; 验证输出内容是否包含预期字符串
(is (re-find #"Changed from 0 to 2" out)))))
🔍 解析#
(with-out-str ...)捕获所有标准输出,并将其作为字符串返回到out。(add-watch a :print #(println "Changed from" %3 "to" %4))添加 watcher,当 atom 值变化时触发,打印变化前后的值。(swap! a + 2)修改 atom 值,从0→2,触发 watcher。(is (re-find ... out))使用正则匹配输出字符串,验证输出内容是否符合预期。
注意:输出可能包含换行符,用 re-find 或 clojure.string/includes? 更稳妥。
2️⃣ 方法二:使用 with-redefs临时替换println(精确控制)#
如果你想完全不产生真实输出,而是直接捕获传给println的参数,可以使用 with-redefs。
示例:
(deftest atom-watcher
(testing "use watcher with redefs"
(let [a (atom 0)
called (atom nil)] ;; 用于保存 println 调用参数
(with-redefs [println (fn [& args] (reset! called args))] ;; 临时替换 println
(add-watch a :print #(println "Changed from" %3 "to" %4))
(swap! a + 2)) ;; 触发 watcher
;; 断言捕获到的参数是否正确
(is (= ["Changed from" 0 "to" 2] @called))
;; 断言 atom 的值
(is (= 2 @a)))))
🔍 详细解析#
1、创建 atom#
(let [a (atom 0)
called (atom nil)]
a:被监听的 atomcalled:保存println的调用参数
**2、临时替换 **println#
(with-redefs [println (fn [& args] (reset! called args))]
with-redefs会在其代码块执行期间临时重定义函数- 将
println替换为一个匿名函数,把参数存入called,而不实际打印
3、添加 watcher 并修改 atom#
(add-watch a :print #(println "Changed from" %3 "to" %4))
(swap! a + 2)
- 当
swap!修改值时,watcher 会调用我们临时重定义的println - 参数
["Changed from" 0 "to" 2]被写入calledatom
4、断言#
(is (= ["Changed from" 0 "to" 2] @called))
(is (= 2 @a))
- 验证捕获到的参数是否正确
- 验证 atom 的最终值
🧠 方法对比#
| 方法 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
with-out-str | 捕获标准输出流 | 简单直观 | 输出带换行符,难区分多次调用 | 验证输出文本 |
with-redefs | 临时替换函数 | 精确断言参数 | 代码稍繁琐 | 验证参数或禁止真实输出 |
3️⃣ 总结与推荐#
- 一般场景 → 用
with-out-str,快速捕获输出文本 - 需要精确断言参数或禁止打印 → 用
with-redefs add-watch回调参数%3和%4分别是变化前后的值,便于在 watcher 中断言
💡 小技巧:封装宏提高可读性
(defmacro is-printed [expected & body]
`(let [out# (with-out-str ~@body)]
(is (clojure.string/includes? out# ~expected))))
使用:
(deftest demo
(is-printed "Hello" (println "Hello, world!")))
这样,你就可以在 Clojure 测试中安全、准确地验证 println 输出内容,无论是捕获文本还是捕获参数。
📎 参考文章#
- 示例代码
未支持的 Notion 内容:bookmark 在 Notion 中打开