是否存在Common Lisp的模拟/存根框架?
EmacsLispMock看起来很棒,但它是一个Emacs lisp框架,我正在寻找可以使用Common Lisp的东西。
有什么建议吗?
答案 0 :(得分:4)
以下内容应该是您正在寻找的内容
(defmacro with-replaced-function (fdef &rest body)
(let ((oldf (gensym))
(result (gensym))
(name (car fdef))
(args (cadr fdef))
(rbody (cddr fdef)))
`(let ((,oldf (symbol-function ',name)))
(setf (symbol-function ',name) (lambda ,args ,@rbody))
(let ((,result (progn ,@body)))
(setf (symbol-function ',name) ,oldf)
,result))))
(defmacro show (x)
`(format t "~a --> ~a~%"
',x ,x))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun foo (x y) (+ x y))
(defun bar (x) (foo x (* x 2)))
(show (bar 42))
(show (with-replaced-function (foo (x y) (* x y))
(bar 42)))
(show (bar 42))
宏只是保存符号当前指向的函数,并将其替换为提供的存根实现。在块结束时,该函数将恢复为原始值。
添加对身体非本地出口的保护可能是有意义的。
另请注意,如果编译器已内联函数调用,那么显然更改函数定义将不起作用。 CL有一个特殊的NOTINLINE声明,可用于防止此问题。
答案 1 :(得分:2)
正如Rainer所指出的那样,文件编译器有时会内联函数,这意味着更改函数定义不会对函数内联的地方产生任何影响。
更改函数名的定义也不会取代函数作为文字对象的使用,例如,如果你在某个变量中保存了''my-stubbed-function。
但是,在某些lisp实现中,您可以定义函数包装器或使用建议来实现此目的:例如,Allegro CL具有fwrappers。在SBCL中,您可以将TRACE与非标准:break函数和自定义* invoke-debugger-hook *一起使用,我确信其他lisp实现也会有类似的东西。
我认为没有人将这些存根方法打包到库中。你能成为第一个! (它会很棒。我喜欢用这样的东西。)
答案 2 :(得分:1)
您不需要CL中的模拟/存根框架。
只需使用方法ovverides创建从类类派生的新CLOS,即可实现存根/模拟,并且已完成。
至于存根,为什么不重新定义功能?
答案 3 :(得分:1)
这不是最简单的方法吗?
> (defun b () 'original)
B
> (setf f #'b)
#<Compiled-function B #xC2C1546>
> (defun a () (funcall f))
A
> (a)
ORIGINAL
> (setf f #'(lambda () 'stub))
#<Anonymous Function #xC2D990E>
> (a)
STUB
> (setf f #'b)
#<Compiled-function B #xC2C1546>
> (a)
ORIGINAL
答案 4 :(得分:1)
我用一个非常类似于@ 6502答案(with-mocked-functions
)的宏编写了一个库,但更为一般。它还提供了with-added-methods
,它允许您为有限的动态范围编写模拟方法。您可以在此处找到它:https://github.com/bytecurry/bytecurry.mocks
答案 5 :(得分:0)
您可以尝试在宏中包装函数重定义
(defmacro with-fun (origfn mockfn &body body)
`(let ((it ,origfn))
(setf ,origfn ,mockfn)
,@body
(setf ,origfn ,it)))
这只是一个想法,你将不得不实现这样的宏。
你可以谷歌进行profile
实现,确切地说,将一个函数替换为另一个函数并添加分析信息。你可以从那里借一些想法。
答案 6 :(得分:0)
几年后,有。我们在Quicklisp中都有cl-mock和mockingbird。
(ql:quickload :mockingbird)
这个也允许检查一个函数是否被调用,如果是这样,有多少次以及哪些参数可以存储单个方法。