2010-11-01 13 views
11

Esiste un framework di simulazione/stub per Common Lisp?Esiste un framework di simulazione/stub per Common Lisp?

EmacsLispMock sembra ottimo, ma è un framework lisp di Emacs e sto cercando qualcosa da usare da Common Lisp.

Qualche suggerimento?

+2

Cosa fa una struttura di derisione/stub? – Xach

+0

@Xach L'idea è di consentire di testare una determinata funzione isolandola, controllando il comportamento di altre funzioni. Quindi, se si ha una funzione A che chiama una funzione B, è possibile eseguire il Bub per restituire sempre 5, o qualcosa, e verificare che A faccia ciò che dovrebbe fare con quel valore di ritorno. In questo modo è possibile verificare che A funzioni senza dover chiamare il B. effettivo. Uno scenario comune è il test del codice che dipende dall'accesso al database, senza dover configurare e configurare un database per ciascun test. –

+0

Probabilmente definirei semplicemente una "funzione di stub B" come (defun b (& rest args) 5) se volessi, in particolare, avere una funzione che ritorna 5. Una volta che è in atto e le funzioni che usano la mia "B" sono state testate, ricarica la definizione corretta. – Vatine

risposta

4

Il seguente dovrebbe fare quello che stai cercando

(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 salva semplicemente la funzione di essere attualmente indicato dal simbolo e lo sostituisce con l'implementazione stub fornito. Alla fine del blocco la funzione viene ripristinata al valore originale.

Avrebbe probabilmente senso aggiungere una protezione contro le uscite non locali dal corpo.

Nota anche che ovviamente la modifica della definizione di una funzione non funzionerà se le chiamate di funzione sono state indicate da un compilatore. CL ha una dichiarazione speciale NOTINLINE che può essere utilizzata per prevenire questo problema.

+6

Va notato che questo potrebbe non funzionare necessariamente in codice compilato, come funzioni potrebbe essere delineato dal compilatore di file (il compilatore può ad esempio assumere che le funzioni nello stesso file rimangano le stesse). Quindi potrebbe essere necessario dichiarare che queste funzioni non sono in linea. –

+0

@Rainer Grazie per aver segnalato che fuori –

+0

protezione contro le uscite non locali è fatto utilizzando 'unwind-protect'. Questo è un modello abbastanza semplice, spesso presente nei macro. – Svante

1

Non è necessaria una struttura di derisione/stub in CL.

Basta creare un nuovo CLOS derivato dalla classe di classe con metodi ovverides per ciò che si desidera stub/mock e il gioco è fatto.

Per quanto riguarda lo stub, perché non ridefinire la funzione?

+2

Grazie per la risposta. Sto anche cercando una soluzione che funzioni con codice che non usi CLOS. Mi piacerebbe essere in grado di interrompere praticamente qualsiasi funzione che chiama un'altra funzione. –

+4

Credo che FLET leghi solo una funzione a un simbolo all'interno di un contesto lessicale specificato. – Vatine

+0

La ridefinizione della funzione funzionerebbe se ci fosse un buon modo per "ripristinare" la definizione in seguito, dal momento che mi piacerebbe essere in grado di eseguire i test senza intaccare le definizioni delle funzioni effettive. Qualche idea su questo? –

1

Non è questo il modo più semplice per farlo?

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

Suggerimento interessante. Quindi, vorrebbe dire che devi scrivere il tuo codice con determinate chiamate di funzione che sono pensate per essere sostituibili, usando funcall piuttosto che una normale chiamata. Forse questo è il modo comune per la chiarezza? Cioè l'implementazione da utilizzare può variare durante il runtime, quindi utilizzare funcall. –

0

Si può cercare di avvolgere funzione di ridefinizione all'interno di una macro

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

Questa è solo un'idea, e si dovrà implementare tale macro. Puoi google per l'implementazione profile che fa esattamente questo, sostituisci una funzione con un'altra e aggiungi informazioni di profilazione. Puoi prendere in prestito alcune idee da lì.

+0

Sembra dolce! Lo proverò sicuramente –

+0

Per i neofiti in cerca di risposte: questa è una macro anaforica e il tuo codice non funzionerà se passi un corpo che contiene il simbolo 'it'. Per maggiori informazioni consulta: https://en.wikipedia.org/wiki/Anaphoric_macro e https://en.wikipedia.org/wiki/Hygienic_macro – tsikov

2

Come fa notare Rainer, il compilatore di file a volte funziona in linea, il che significa che la modifica della definizione della funzione non avrà alcun effetto nei punti in cui la funzione è stata allineata.

Anche la modifica della definizione del nome della funzione non sostituirà l'utilizzo della funzione come oggetto letterale, ad esempio, se si è salvata la # funzione my-stubbed in una variabile da qualche parte.

Tuttavia, in alcune implementazioni chiare è possibile definire wrapper funzione o utilizzare i consigli per ottenere ciò: Ad esempio, Allegro CL ha fwrappers. In SBCL, è possibile utilizzare TRACE con una funzione non standard: break e un hook * debugger-debug * personalizzato, e sono sicuro che altre implementazioni di Lisp avranno qualcosa di simile.

Non penso che nessuno abbia impacchettato questi metodi di stub in una libreria. Potresti essere il primo! (E sarebbe fantastico. Mi piacerebbe usare qualcosa di simile.)

1

Ho scritto una libreria con una macro molto simile alla risposta di @ 6502 (with-mocked-functions), ma un po 'più generale. Fornisce anche with-added-methods che consente di scrivere metodi di simulazione per un ambito dinamico limitato.Potete trovarlo qui: https://github.com/bytecurry/bytecurry.mocks

0

Alcuni anni dopo, c'è. Abbiamo cl-mock e mockingbird entrambi in Quicklisp.

(ql:quickload :mockingbird) 

Questo permette anche di verificare se una funzione è stata chiamata, in caso affermativo quante volte e con quali argomenti ed è possibile stub metodi individuali.