2010-11-01 11 views
11

¿Existe un marco de burla/stubbing para Common Lisp?¿Existe un marco de burla/stubbing para Common Lisp?

EmacsLispMock se ve muy bien, pero es un marco de ceceo de Emacs, y estoy buscando algo para usar de Common Lisp.

¿Alguna sugerencia?

+2

¿Qué hace un marco de burla/stubbing? – Xach

+0

@Xach La idea es permitirle probar una función determinada de forma aislada, controlando el comportamiento de otras funciones. Entonces, si tiene una función A que llama a una función B, puede indicar B para devolver siempre 5, o algo así, y verificar que A haga lo que se supone que debe hacer con ese valor de retorno. De esta forma puede verificar que A funciona sin tener que llamar a la B real. Un escenario común es probar código que depende del acceso a la base de datos, sin tener que configurar y configurar una base de datos para cada prueba. –

+0

Probablemente definiría una "función B" como (defun b (y restos args) 5) si quisiera, específicamente, tengo una función que devuelve 5. UNA vez que está en su lugar y las funciones que usan mi "B" han sido probadas, recarga la definición adecuada. – Vatine

Respuesta

4

Lo siguiente debe hacer lo que usted está buscando

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

La macro simplemente guarda la función que se está actualmente indicado por el símbolo y lo reemplaza con la ejecución de código auxiliar proporcionado. Al final del bloque, la función se restablece al valor original.

Probablemente tenga sentido agregar una protección contra las salidas no locales del cuerpo.

Tenga en cuenta también que obviamente cambiar la definición de una función no funcionará si las llamadas a la función han sido inlineadas por un compilador. CL tiene una declaración especial NOTINLINE que se puede usar para prevenir este problema.

+6

. Cabe señalar que esto podría no funcionar necesariamente en código compilado, como funciones podría estar delimitado por el compilador de archivos (el compilador puede suponer, por ejemplo, que las funciones en el mismo archivo permanecen iguales). Por lo tanto, podría ser necesario declarar que estas funciones no están en línea. –

+0

@Rainer Gracias por señalar que –

+0

La protección contra las salidas no locales se realiza usando 'unwind-protect'. Es un patrón bastante básico, que a menudo se encuentra en las macros. – Svante

1

No necesita un marco de burla/stubbing en CL.

Simplemente cree nuevos CLOS derivados de su clase con métodos para evitar lo que quiera resguardar o simular y listo.

En cuanto al stubbing, ¿por qué no simplemente redefinir la función?

+2

Gracias por su respuesta. También estoy buscando una solución que funcione con código que no use CLOS. Me gustaría poder resumir básicamente cualquier función que llame a otra función. –

+4

Creo que FLET solo vincula una función a un símbolo dentro de un contexto léxico específico. – Vatine

+0

La redefinición de la función funcionaría si hay una buena manera de "restaurar" la definición posteriormente, ya que me gustaría poder ejecutar las pruebas sin estropear las definiciones de funciones reales. Alguna idea sobre eso? –

1

¿No es esta la manera más simple de hacer esto?

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

Sugerencia interesante. Por lo tanto, significa que debe escribir su código con ciertas llamadas a función que se pretende que sean reemplazables, utilizando funcall en lugar de una llamada ordinaria. Tal vez esa es la forma común de lisp? Es decir. la implementación a usar puede variar durante el tiempo de ejecución, por lo tanto, use funcall. –

0

Usted puede tratar de envolver función de re-definición dentro de una macro

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

Esto es sólo una idea, y que tendrá que poner en práctica tales macro. Puede buscar en Google la implementación profile que hace exactamente eso, reemplazar una función por otra y agregar información de perfil. Puedes tomar algunas ideas de allí.

+0

¡Parece dulce! Definitivamente intentarlo –

+0

Para los novatos que buscan respuestas: Esta es una macro anafórica y su código no funcionará si pasa un cuerpo que contiene el símbolo 'it'. Para obtener más información, consulte: https://en.wikipedia.org/wiki/Anaphoric_macro y https://en.wikipedia.org/wiki/Hygienic_macro – tsikov

2

Como Rainer señala, el compilador de archivos a veces inserta funciones, lo que significa que cambiar la definición de la función no tendrá ningún efecto en los lugares donde la función estaba en línea.

Cambiar la definición del nombre de la función tampoco reemplazará el uso de la función como un objeto literal, por ejemplo, si guardó # 'my-stubbed-function en alguna variable.

Sin embargo, en algunas implementaciones de lisp puede definir envoltorios de funciones o utilizar consejos para lograr esto: por ejemplo, Allegro CL tiene fwrappers. En SBCL, podría usar TRACE con una función break no estándar y un * invoke-debugger-hook * personalizado, y estoy seguro de que otras implementaciones lisp tendrán algo similar.

No creo que nadie haya empaquetado estos métodos de anotación en una biblioteca. ¡Podrías ser el primero! (Y sería genial. Me encantaría usar algo como esto.)

1

Escribí una biblioteca con una macro muy similar a la respuesta de @ 6502 (with-mocked-functions), pero un poco más general. También proporciona with-added-methods que le permite escribir métodos simulados para un alcance dinámico limitado.Lo puedes encontrar aquí: https://github.com/bytecurry/bytecurry.mocks

0

Unos años más tarde, la hay. Tenemos cl-mock y mockingbird ambos en Quicklisp.

(ql:quickload :mockingbird) 

Ésta también permite comprobar si una función se llama, en caso afirmativo cuántas veces y con qué argumentos y es posible código auxiliar métodos individuales.