我想测试一个使用 gensyms 的宏。例如,如果我想测试这个:
(defmacro m1
[x f]
`(let [x# ~x]
(~f x#)))
我可以使用宏扩展...
(macroexpand-1 '(m1 2 inc))
...要得到...
(clojure.core/let [x__3289__auto__ 2] (inc x__3289__auto__))
这对一个人来说很容易验证是否正确。
但是我怎样才能以一种实用、干净的自动化方式对此进行测试呢?gensym 不稳定。
(是的,我知道特定的宏观示例并不引人注目,但这个问题仍然是公平的。)
我意识到 Clojure 表达式可以被视为数据(它是一种同音语言),所以我可以像这样分解结果:
(let [result (macroexpand-1 '(m1 2 inc))]
(nth result 0) ; clojure.core/let
(nth result 1) ; [x__3289__auto__ 2]
((nth result 1) 0) ; x__3289__auto__
((nth result 1) 0) ; 2
(nth result 2) ; (inc x__3289__auto__)
(nth (nth result 2) 0) ; inc
(nth (nth result 2) 1) ; x__3289__auto__
)
但这很笨拙。有没有更好的方法?也许有可以派上用场的数据结构“验证”库?也许解构会让这更容易?逻辑编程?
更新/评论:
虽然我很欣赏有经验的人说“不要测试宏扩展本身”的建议,但它并没有直接回答我的问题。
通过测试宏扩展来“单元测试”宏有什么不好?测试扩展是合理的——事实上,许多人在 REPL 中“手动”测试他们的宏——那么为什么不自动测试呢?我认为没有充分的理由不这样做。我承认测试宏扩展比测试结果更脆弱,但做前者仍然有价值。你也可以测试功能——你可以两者都做!这不是一个非此即彼的决定。
这是我的心理解释。人们不测试宏扩展的原因之一是它目前有点痛苦。一般而言,人们往往会在看似困难的情况下合理地反对做某事,而与它的内在价值无关。是的——这正是我问这个问题的原因!如果这很容易,我认为人们会更频繁地这样做。如果这很容易,他们就不太可能通过给出“不值得做”的答案来合理化。
我也理解“您不应该编写复杂的宏”的论点。当然。但我们希望人们不要去想“如果我们鼓励一种不测试宏的文化,那么这将阻止人们编写复杂的宏”。这样的论点是愚蠢的。如果您有一个复杂的宏扩展,那么测试它是否按预期工作是一件明智的事情。即使是简单的事情,我个人也不会接受测试,因为我经常对错误可能来自简单的错误感到惊讶。