Есть ли издевательская /stubbing фреймворк для Common Lisp?

Есть ли насмешливая/заглушка для Common Lisp?

EmacsLispMock отлично смотрится, но это среда Emacs lisp, и я ищу что-то для использования из Common Lisp.

Любые предложения?

Ответ 1

Следующее должно делать то, что вы ищете

(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, которое можно использовать для предотвращения этой проблемы.

Ответ 2

Как указывает Райнер, компилятор файлов иногда включает функции, что означает, что изменение определения функции не будет иметь никакого эффекта в тех местах, где функция была встроена.

Изменение определения имени функции также не заменит использование функции как литерального объекта, например, если вы сохранили # my-stubbed-function в переменной где-то.

Однако в некоторых реализациях lisp вы можете определить обертки функций или использовать советы для достижения этого: например, Allegro CL имеет fwrappers. В SBCL вы можете использовать TRACE с нестандартной функцией break и пользовательским * invoke-debugger-hook *, и я уверен, что другие lisp будут иметь что-то подобное.

Я не думаю, что кто-то упаковал эти методы stubbing в библиотеку. Ты мог быть первым! (И это было бы потрясающе. Я бы хотел использовать что-то вроде этого.)

Ответ 3

Вам не нужна фальшивка /stubbing framework в CL.

Просто создайте новый CLOS, полученный из вашего класса, с помощью методов ovverides для того, что вы хотите заглушить/вымыться, и все готово.

Что касается stubbing, почему бы не просто переопределить функцию?

Ответ 4

Разве это не самый простой способ сделать это?

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

Ответ 5

Я написал библиотеку с макросом, очень похожим на ответ @6502 (with-mocked-functions), но немного более общим. Он также предоставляет with-added-methods, который позволяет писать макетные методы для ограниченной динамической области. Вы можете найти его здесь: https://github.com/bytecurry/bytecurry.mocks

Ответ 6

Вы можете попробовать перевернуть функцию переопределения внутри макроса

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

Это всего лишь идея, и вам придется реализовать такой макрос. Вы можете использовать google для реализации profile, которая делает именно это, заменяет одну функцию на другую и добавляет информацию профилирования. Вы можете брать некоторые идеи оттуда.

Ответ 7

Несколько лет спустя. У нас cl-mock и mockingbird оба в Quicklisp.

(ql:quickload :mockingbird)

Это также позволяет проверить, была ли вызвана функция, если да, сколько раз и с какими аргументами и можно ли заглушить отдельные методы.