Существует ли фреймворк для насмешек/заглушек для Common Lisp?

Существует ли фреймворк для насмешек/заглушек для Common Lisp?

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

Какие-либо предложения?


person Erik Öjebo    schedule 01.11.2010    source источник
comment
Что делает mocking/stubbing framework?   -  person Xach    schedule 01.11.2010
comment
@Xach Идея состоит в том, чтобы позволить вам протестировать данную функцию изолированно, контролируя поведение других функций. Итак, если у вас есть функция A, которая вызывает функцию B, вы можете заглушить B, чтобы она всегда возвращала 5 или что-то в этом роде, и убедиться, что A делает то, что должна делать с этим возвращаемым значением. Таким образом, вы можете убедиться, что A работает, не вызывая фактический B. Распространенным сценарием является тестирование кода, который зависит от доступа к базе данных, без необходимости устанавливать и настраивать базу данных для каждого теста.   -  person Erik Öjebo    schedule 01.11.2010
comment
Я бы, вероятно, просто определил функцию-заглушку B как (defun b (&rest args) 5), если бы я хотел, в частности, чтобы функция возвращала 5. После того, как это на месте и функции, использующие мой B, были протестированы, перезагрузите правильное определение.   -  person Vatine    schedule 01.11.2010
comment
@vatine Это было бы полезно во время разработки, если вы хотите реализовать A до B, но если вы затем переопределите B с правильной реализацией, вы потеряете возможность выполнять изолированные регрессионные тесты для A. Например, если A должен быть в состоянии обрабатывать целые числа и nil из B, я бы хотел, чтобы регрессионные тесты для обоих этих случаев были изолированы от фактической реализации B (поскольку это может быть медленным и трудным для контроля, если оно зависит от файловой системы, базы данных и т. д.).   -  person Erik Öjebo    schedule 01.11.2010
comment
Если вы сделаете что-то вроде (defvar b-return-value), а затем определите b как (defun b (&rest args) b-return-value)), вы можете обернуть LET, повторно связывающий b-return-value. Тем не менее, это означает, что вам нужно иметь одно определение для тестирования и одно для реальной работы.   -  person Vatine    schedule 02.11.2010
comment
Друзья-гуглеры: у нас есть cl-mock и mockingbird в Quicklisp.   -  person Ehvince    schedule 20.09.2017


Ответы (7)


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

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

person 6502    schedule 03.11.2010
comment
Следует отметить, что это может не обязательно работать в скомпилированном коде, поскольку функции могут быть встроены компилятором файлов (например, компилятор может предположить, что функции в одном и том же файле остаются прежними). Поэтому может потребоваться объявить эти функции не встроенными. - person Rainer Joswig; 04.11.2010
comment
Защита от нелокальных выходов осуществляется с помощью unwind-protect. Это довольно простой шаблон, часто встречающийся в макросах. - person Svante; 10.02.2017

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

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

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

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

person antifuchs    schedule 18.11.2010

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

> (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
person Vijay Mathew    schedule 02.11.2010
comment
Интересное предложение. Таким образом, это будет означать, что вы должны написать свой код с определенными вызовами функций, которые должны быть заменены, используя funcall, а не обычный вызов. Может быть, это общий способ шепелявости? т.е. используемая реализация может меняться во время выполнения, поэтому используйте funcall. - person Erik Öjebo; 02.11.2010

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

person Thayne    schedule 05.04.2015

Вам не нужна насмешливая/заглушенная структура в CL.

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

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

person Marko    schedule 01.11.2010
comment
Спасибо за Ваш ответ. Я также ищу решение, которое будет работать с кодом, не использующим CLOS. Я хотел бы иметь возможность заглушить практически любую функцию, которая вызывает другую функцию. - person Erik Öjebo; 01.11.2010
comment
Я считаю, что FLET связывает функцию с символом только в определенном лексическом контексте. - person Vatine; 01.11.2010
comment
Переопределение функции будет работать, если есть хороший способ впоследствии восстановить определение, поскольку я хотел бы иметь возможность запускать тесты, не испортив фактические определения функций. Любые идеи по этому поводу? - person Erik Öjebo; 02.11.2010

Через несколько лет есть. У нас есть cl-mock и mockingbird в Quicklisp.

(ql:quickload :mockingbird)

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

person Ehvince    schedule 19.09.2017

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

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

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

person Marko    schedule 02.11.2010
comment
Выглядит мило! я обязательно попробую - person Erik Öjebo; 03.11.2010
comment
Для новичков, ищущих ответы: это анафорический макрос, и ваш код не будет работать, если вы передадите тело, содержащее символ it. Для получения дополнительной информации посетите: en.wikipedia.org/wiki/Anaphoric_macro и en.wikipedia.org/wiki/Hygienic_macro - person tsikov; 27.09.2016