Dav*_* J. 7 testing macros clojure gensym
我想测试一个使用gensyms的宏.例如,如果我想测试这个:
(defmacro m1
[x f]
`(let [x# ~x]
(~f x#)))
Run Code Online (Sandbox Code Playgroud)
我可以用宏扩展......
(macroexpand-1 '(m1 2 inc))
Run Code Online (Sandbox Code Playgroud)
...要得到...
(clojure.core/let [x__3289__auto__ 2] (inc x__3289__auto__))
Run Code Online (Sandbox Code Playgroud)
这对于一个人来说很容易验证是正确的.
但是,我如何以实用,干净的自动化方式对其进行测试?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__
)
Run Code Online (Sandbox Code Playgroud)
但这很笨拙.还有更好的方法吗?也许有数据结构'验证'库可以派上用场?也许解构会让这更容易?逻辑编程?
更新/评论:
虽然我很欣赏有经验的人的建议,他们说"不要测试宏观扩张本身",但它没有直接回答我的问题.
通过测试宏扩展"单元测试"宏有什么不好?测试扩展是合理的 - 事实上,许多人在REPL中"手动"测试他们的宏 - 那么为什么不自动测试呢?我没有理由不这样做.我承认测试宏扩展比测试结果更脆弱,但做前者仍然有价值.您也可以测试功能 - 您可以同时执行这两项功能!这不是一个或两个决定.
这是我的心理解释.人们不测试宏观扩张的原因之一是它目前有点痛苦.一般来说,人们经常会在看似困难的时候采取合理的做法,而不论其内在价值如何.是的 - 这就是我问这个问题的原因!如果这很容易,我认为人们会更频繁地这样做.如果这很容易,他们就不太可能通过给出"不值得做"的答案来合理化.
我也理解"你不应该写一个复杂的宏"的论点.当然.但是,我们希望人们不要认为"如果我们鼓励不测试宏的文化,那么这将阻止人们编写复杂的宏." 这样的说法很愚蠢.如果你有一个复杂的宏扩展,测试它是否正常工作是一个明智的事情.我本人并不是在测试甚至是简单的东西,因为我常常惊讶于错误可能来自简单的错误.
不要测试它是如何工作的(它的扩展),测试它是否有效.如果您测试特定的扩展,那么您将被链接到该实施策略; 相反,只需测试(m1 2 inc)返回3,并且无论其他测试用例是否需要安慰您的良心,然后您可以对您的宏工作感到高兴.
这可以通过元数据来完成。您的宏输出一个列表,其中可以附加元数据。只需将 gensym->var 映射添加到其中,然后使用它们进行测试。
所以你的宏看起来像这样:
(defmacro m1 [x f]
(let [xsym (gensym)]
(with-meta
`(let [~xsym ~x]
(~f ~xsym))
{:xsym xsym})))
Run Code Online (Sandbox Code Playgroud)
宏的输出现在有一个带有 gensyms 的映射:
(meta (macroexpand-1 '(m1 a b)))
=> {:xsym G__24913}
Run Code Online (Sandbox Code Playgroud)
要测试宏扩展,您可以执行以下操作:
(let [out (macroexpand-1 `(m1 a b))
xsym (:xsym (meta out))
target `(clojure.core/let [~xsym a] (b ~xsym))]
(= out target))
Run Code Online (Sandbox Code Playgroud)
要解决为什么要这样做的问题:我通常编写宏的方式是首先生成目标代码(即我希望宏输出的内容),测试它是否做正确的事情,然后从中生成宏。预先拥有已知良好的代码可以让我针对宏进行 TDD;特别是,我可以调整宏,运行测试,如果失败,clojure.test将向我显示实际与目标的对比,我可以目视检查。
| 归档时间: |
|
| 查看次数: |
803 次 |
| 最近记录: |