Gre*_*bet 13 lisp macros unit-testing common-lisp
我发现很难推断宏观扩张,并且想知道测试它们的最佳实践是什么.
所以如果我有一个宏,我可以通过执行一个级别的宏扩展macroexpand-1.
(defmacro incf-twice (n)
`(progn
(incf ,n)
(incf ,n)))
Run Code Online (Sandbox Code Playgroud)
例如
(macroexpand-1 '(incf-twice n))
Run Code Online (Sandbox Code Playgroud)
评估为
(PROGN (INCF N) (INCF N))
Run Code Online (Sandbox Code Playgroud)
将它变成对宏的测试似乎很简单.
(equalp (macroexpand-1 '(incf-twice n))
'(progn (incf n) (incf n)))
Run Code Online (Sandbox Code Playgroud)
是否有用于组织宏测试的既定惯例?还有,是否有一个库来总结s表达式之间的差异?
通常,测试宏不是Lisp和Common Lisp的强大部分之一.Common Lisp(和Lisp方言一般)使用过程宏.宏可以依赖于运行时上下文,编译时上下文,实现等.它们也可能有副作用(比如在编译时环境中注册事物,在开发环境中注册事物等等).
所以有人可能想测试一下:
loop,defstruct...宏.loop和defstruct.从上面的列表可以推断,在开发宏时最好最小化所有这些问题区域.但是:那里真的有非常复杂的宏.真可怕的.特别是那些习惯于实现新域特定语言的人.
使用equalp比较代码之类的东西仅适用于相对简单的宏.宏通常会引入新的,未加工的和唯一的符号.因此equalp将无法与那些人合作.
示例:(rotatef a b)看起来很简单,但扩展实际上很复杂:
CL-USER 28 > (pprint (macroexpand-1 '(rotatef a b)))
(PROGN
(LET* ()
(LET ((#:|Store-Var-1234| A))
(LET* ()
(LET ((#:|Store-Var-1233| B))
(PROGN
(SETQ A #:|Store-Var-1233|)
(SETQ B #:|Store-Var-1234|))))))
NIL)
Run Code Online (Sandbox Code Playgroud)
#:|Store-Var-1233| 是一个符号,它是未处理的并由宏新创建的.
另一个具有复杂扩展的简单宏形式将是(defstruct s b).
因此,需要一个s表达式模式匹配器来比较扩展.有一些可用,它们在这里很有用.需要在测试模式中确保生成的符号在需要时是相同的.
还有s-expression diff工具.例如diff-sexp.
我同意Rainer Joswig 的回答;一般来说,这是一个非常难解决的任务,因为宏可以做很多事情。但是,我要指出的是,在许多情况下,对宏进行单元测试的最简单方法是让宏尽可能少地执行操作。在许多情况下,宏的最简单实现只是围绕更简单函数的语法糖。例如,在 Common Lisp 中有一种典型的with -...宏模式(例如with-open-file),其中宏简单地封装了一些样板代码:
(defun make-frob (frob-args)
;; do something and return the resulting frob
(list 'frob frob-args))
(defun cleanup-frob (frob)
(declare (ignore frob))
;; release the resources associated with the frob
)
(defun call-with-frob (frob-args function)
(let ((frob (apply 'make-frob frob-args)))
(unwind-protect (funcall function frob)
(cleanup-frob frob))))
(defmacro with-frob ((var &rest frob-args) &body body)
`(call-with-frob
(list ,@frob-args)
(lambda (,var)
,@body)))
Run Code Online (Sandbox Code Playgroud)
这里的前两个函数make-frob和cleanup-frob对于单元测试来说相对简单。该呼叫与-FROB是有点困难。这个想法是它应该处理创建 frob 并确保清理调用发生的样板代码。这有点难以检查,但如果样板仅依赖于一些定义良好的接口,那么您可能能够创建一个可以检测是否正确清理的 frob 模型。最后,with-frob宏非常简单,您可能可以按照您一直在考虑的方式对其进行测试,即检查其扩展。或者你可能会说它很简单,你不需要测试它。
另一方面,如果您正在查看一个更复杂的宏,例如loop,它本身就是一种编译器,您几乎可以肯定已经在一些单独的函数中拥有扩展逻辑。例如,你可能有
(defmacro loop (&body body)
(compile-loop body))
Run Code Online (Sandbox Code Playgroud)
在这种情况下,您真的不需要测试loop,您需要测试compile-loop,然后您又回到了通常的单元测试领域。
| 归档时间: |
|
| 查看次数: |
205 次 |
| 最近记录: |