elisp用stub/mockフレームワーク作成中

elisp用のmockやstubのフレームワークってないの? - http://rubikitch.com/に移転しました

ないようなので作成中。名前はel-mock.elだ。まだ途中なので経過をブログに晒しておく。

(eval-when-compile (require 'cl))

(defun with-stub-function (stub-spec body-func)
  (loop for (funcsym _ value) on stub-spec
        when (eq _ '=>)
        collecting (progn
                     (condition-case e (ad-deactivate funcsym) (error nil))
                     (list funcsym value)) into pair
        finally 
        (return
         (unwind-protect 
             (eval `(flet (,@(loop for (funcsym value) in pair
                                   collecting
                                   `(,funcsym (&rest x) ,value)))
                      (funcall body-func)))
           (loop for (funcsym value) in pair
                 do (condition-case e (ad-activate funcsym) (error nil)))))))

(defmacro with-stub (stub-spec &rest body)
  `(with-stub-function ',stub-spec (lambda () ,@body)))
(put 'with-stub 'lisp-indent-function 1)

特定の範囲のみで関数を偽装するwith-stubマクロ。偽装された関数は、どんな引数で呼ばれても常に同じ返り値を返す。fletでも偽装できるが、adviceを考慮してないのでスタブとしてはうまく機能しない。

(with-stub (偽装する関数名1 => 偽装関数の返り値1
            偽装する関数名2 => 偽装関数の返り値2
            ...)
   式...)

な感じで記述する。「=>」は趣味。

たとえば、set-window-start等「画面」に依存する関数を偽装することで、通常ならば目視確認しかできなかったテストが自動化できるようになる…はず。

el-expectations.elによるユニットテストはこんなの。

(expectations
    (desc "with-stub")
    (expect 2
      (with-stub (foo => 2)
        (foo 1 2 3)))
    (expect 3
      (defun defined-func (x) 3)
      (unwind-protect
          (with-stub (defined-func => 3)
            (defined-func 3))
        (fmakunbound 'defined-func)))
    (expect "xx"
      (with-stub (find-file => "xx")
        (find-file "xx")))
    (expect 5
      (with-stub (a => 3   b => 2)
        (+ (a 999) (b 7)))))

モックについては、引数と返り値のverifyを行うようにする。しかし、どのようなDSLにするかは決めかねている。迷う。

追記

Lispはカッコだらけの言語です、はいw その美しさ、強さに気付くまでに何年かかったんだろうか…