2010-11-01 26 views
11

Có khung mocking/stubbing cho Common Lisp không?Có khung mocking/stubbing cho Common Lisp không?

EmacsLispMock trông rất tuyệt, nhưng đó là khung công cụ của Emacs, và tôi đang tìm một thứ gì đó để sử dụng từ Common Lisp.

Mọi đề xuất?

+2

Khung làm việc giả mạo/khoanh vùng là gì? – Xach

+0

@Xach Ý tưởng là để cho phép bạn kiểm tra một hàm nhất định trong sự cô lập, bằng cách kiểm soát hành vi của các chức năng khác. Vì vậy, nếu bạn có một hàm A gọi hàm B, bạn có thể stub B để luôn trả về 5, hoặc một cái gì đó, và xác minh rằng A làm những gì nó được cho là phải làm với giá trị trả về đó. Bằng cách đó bạn có thể xác minh rằng A hoạt động mà không cần phải gọi thực tế B. Một kịch bản phổ biến là mã thử nghiệm phụ thuộc vào truy cập cơ sở dữ liệu, mà không cần phải thiết lập và cấu hình cơ sở dữ liệu cho mỗi thử nghiệm. –

+0

Tôi có lẽ chỉ cần xác định một "hàm B" như (defun b (& rest args) 5) nếu tôi muốn, cụ thể, có một hàm trả về 5. ONce được đặt đúng chỗ và các hàm sử dụng "B" của tôi đã được kiểm tra, tải lại định nghĩa thích hợp. – Vatine

Trả lời

4

Sau đây nên làm những gì bạn đang tìm kiếm

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

vĩ mô chỉ đơn giản là tiết kiệm các chức năng được hiện chỉ bằng ký hiệu và thay thế nó bằng việc thực hiện còn sơ khai cung cấp. Ở cuối khối, hàm được khôi phục về giá trị ban đầu.

Nó có thể có ý nghĩa để thêm một bảo vệ chống lại lối thoát không phải địa phương từ cơ thể.

Cũng lưu ý rằng rõ ràng việc thay đổi định nghĩa hàm sẽ không hoạt động nếu các cuộc gọi hàm được biên dịch bởi trình biên dịch. CL có một tuyên bố đặc biệt NOTINLINE có thể được sử dụng để ngăn chặn vấn đề này.

+6

Cần lưu ý rằng điều này có thể không nhất thiết phải hoạt động trong mã được biên dịch, như các hàm có thể được biên dịch bởi trình biên dịch tệp (trình biên dịch có thể giả định rằng các hàm trong cùng một tệp vẫn giữ nguyên). Vì vậy, nó có thể là cần thiết để khai báo các chức năng này để không được inlined. –

+0

@Rainer Cảm ơn bạn đã chỉ ra rằng –

+0

Bảo vệ chống lại các lối thoát không phải địa phương được thực hiện bằng cách sử dụng 'bảo vệ thư giãn'. Đó là một mẫu khá cơ bản, thường được tìm thấy trong các macro. – Svante

1

Bạn không cần khung mocking/stubbing trong CL.

Chỉ cần tạo mới CLOS có nguồn gốc từ lớp học của bạn với các phương pháp ovverides cho những gì bạn muốn stub/mock và bạn đã làm xong.

Đối với stubbing, tại sao không chỉ xác định lại chức năng?

+2

Cảm ơn câu trả lời của bạn. Tôi cũng đang tìm một giải pháp có thể làm việc với mã không sử dụng CLOS. Tôi muốn để có thể stub về cơ bản bất kỳ chức năng gọi một chức năng khác. –

+4

Tôi tin rằng FLET chỉ liên kết một hàm với một ký hiệu trong một ngữ cảnh từ vựng được chỉ định. – Vatine

+0

Xác định lại chức năng sẽ hoạt động nếu có một cách tốt để "khôi phục" định nghĩa sau đó, vì tôi muốn có thể chạy thử nghiệm mà không làm rối tung các định nghĩa chức năng thực tế. Bất kỳ ý tưởng về điều đó? –

1

Đây không phải là cách đơn giản nhất để thực hiện việc này?

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

Đề xuất thú vị. Vì vậy, nó sẽ có nghĩa là bạn phải viết mã của bạn với các cuộc gọi chức năng nhất định có nghĩa là để có thể thay thế, bằng cách sử dụng funcall chứ không phải là một cuộc gọi bình thường. Có lẽ đó là cách lisp phổ biến? I E. việc triển khai để sử dụng có thể khác nhau trong thời gian chạy, do đó sử dụng funcall. –

0

Bạn có thể cố gắng quấn chức năng tái định nghĩa bên trong một macro

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

Đây chỉ là một ý tưởng, và bạn sẽ phải thực hiện vĩ mô như vậy. Bạn có thể google để thực hiện profile thực hiện chính xác điều đó, thay thế một chức năng bằng một chức năng khác và thêm thông tin lược tả. Bạn có thể mượn một số ý tưởng từ đó.

+0

Có vẻ ngọt ngào! Tôi chắc chắn sẽ cung cấp cho nó một thử –

+0

Đối với những người mới tìm kiếm câu trả lời: Đây là một macro ẩn và mã của bạn sẽ không hoạt động nếu bạn vượt qua một cơ thể có chứa biểu tượng 'nó'. Để biết thêm thông tin, hãy xem https://en.wikipedia.org/wiki/Anaphoric_macro và https://en.wikipedia.org/wiki/Hygienic_macro – tsikov

2

Khi Rainer chỉ ra, trình biên dịch tệp đôi khi có chức năng in, điều này có nghĩa là việc thay đổi định nghĩa hàm sẽ không có bất kỳ hiệu ứng nào ở những nơi mà hàm được inlined.

Việc thay đổi định nghĩa tên chức năng cũng sẽ không thay thế việc sử dụng hàm làm đối tượng theo nghĩa đen, ví dụ: nếu bạn đã lưu # 'hàm-stubbed của tôi vào một biến ở đâu đó.

Tuy nhiên, trong một số triển khai lisp bạn có thể xác định hàm bao hàm hoặc sử dụng lời khuyên để đạt được điều này: Ví dụ: Allegro CL có fwrappers. Trong SBCL, bạn có thể sử dụng TRACE với một chức năng không chuẩn: phá vỡ và một tùy chỉnh * invoke-debugger-hook *, và tôi chắc chắn rằng các triển khai lisp khác sẽ có một cái gì đó tương tự.

Tôi không nghĩ có ai đã đóng gói các phương pháp này vào thư viện. Bạn có thể là người đầu tiên! (Và nó sẽ là tuyệt vời. Tôi muốn sử dụng một cái gì đó như thế này.)

1

Tôi đã viết một thư viện với một macro rất giống với câu trả lời của @ 6502 (with-mocked-functions), nhưng tổng quát hơn một chút. Nó cũng cung cấp with-added-methods cho phép bạn viết các phương thức giả cho một phạm vi động giới hạn.Bạn có thể tìm thấy ở đây: https://github.com/bytecurry/bytecurry.mocks

0

Một vài năm sau, có. Chúng tôi có cl-mockmockingbird cả trong Quicklisp.

(ql:quickload :mockingbird) 

Điều này cũng cho phép kiểm tra xem chức năng có được gọi hay không và đối số nào có thể phân tích các phương thức riêng lẻ.

Các vấn đề liên quan