开发者

Is there a mocking/stubbing framework for Common Lisp?

开发者 https://www.devze.com 2023-01-23 03:19 出处:网络
Is there a mocking/stubbing framework for Common Lisp? EmacsLis开发者_如何转开发pMock looks great, but it is an Emacs lisp framework, and I\'m looking for something to use from Common Lisp.

Is there a mocking/stubbing framework for Common Lisp?

EmacsLis开发者_如何转开发pMock looks great, but it is an Emacs lisp framework, and I'm looking for something to use from Common Lisp.

Any suggestions?


The following should do what you're looking for

(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))

The macro simply saves the function being currently pointed by the symbol and replaces it with the provided stub implementation. At the end of the block the function is restored to the original value.

It would probably make sense to add a protection against non-local exits from the body.

Note also that obviously changing a function definition is not going to work if function calls have been inlined by a compiler. CL has a special NOTINLINE declaration that can be used to prevent this problem.


As Rainer points out, the file compiler sometimes inlines functions, which means changing the function definition won't have any effect in the places where the function was inlined.

Changing the function name's definition also won't replace use of the function as a literal object, for example, if you saved #'my-stubbed-function in a variable somewhere.

However, in some lisp implementations you can define function wrappers or use advice to achieve this: For example, Allegro CL has fwrappers. In SBCL, you could use TRACE with a nonstandard :break function and a custom *invoke-debugger-hook*, and I'm sure other lisp implementations will have something similar.

I don't think anyone has packaged these stubbing methods into a library. You could be the first! (And it would be awesome. I'd love to use something like this.)


A few years later, there is. We have cl-mock and mockingbird both in Quicklisp.

(ql:quickload :mockingbird)

This one also allows to check if a function was called, if so how many times and with which arguments and it's possible to stub individual methods.


Ain't this simplest way to do this?

> (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


I wrote a library with a macro very similar to @6502's answer (with-mocked-functions), but a little more general. It also provides with-added-methods which allows you to write mock methods for a limited dynamic scope. You can find it here: https://github.com/bytecurry/bytecurry.mocks


You don't need a mocking/stubbing framework in CL.

Just create new CLOS derived from your class class with methods ovverides for what you want to stub/mock and you are done.

As for stubbing, why not just redefine function?


You can try to wrap function re-definition inside a macro

(defmacro with-fun (origfn mockfn &body body)
  `(let ((it ,origfn))
      (setf ,origfn ,mockfn)
     ,@body
      (setf ,origfn ,it)))

This is just an idea, and you will have to implement such macro. You can google for profile implementation which does exactly that, replace one function with another and add profiling information. You can borrow some ideas from there.

0

精彩评论

暂无评论...
验证码 换一张
取 消