Clojure宏检查是否调用了函数
我在一个月前开始使用Clojure,为了进行集成测试,我开发了一个小宏来检查一个函数是否是从其他函数调用的。宏当前看起来如下所示:Clojure宏检查是否调用了函数,clojure,macros,jvm,Clojure,Macros,Jvm,我在一个月前开始使用Clojure,为了进行集成测试,我开发了一个小宏来检查一个函数是否是从其他函数调用的。宏当前看起来如下所示: (defmacro called? [f val body] (let [flag (gensym `flag)] `(with-redefs-fn {~f (fn [& rest#] (def ~flag nil) ~val)} #(do (try (~body)
(defmacro called?
[f val body]
(let [flag (gensym `flag)]
`(with-redefs-fn
{~f (fn [& rest#] (def ~flag nil) ~val)}
#(do
(try
(~body)
(catch Throwable e#))
(if (resolve '~flag)
(bound? (resolve '~flag))
false)))))
用法:
user=> (called? #'clojure.core/println nil #(println "hey"))
true
user=> (called? #'clojure.core/println nil #(print "hey"))
heyfalse
(defn my-fn [n] (inc n))
(have-been-called? my-fn)
;; => false
(instrumenting my-fn (dec 1))
;; => 0
(have-been-called? my-fn)
;; => false
(instrumenting my-fn (my-fn 1))
;; => 2
(have-been-called? my-fn)
;; => true
(reset-counters!)
(have-been-called? my-fn)
;; => false
(called? my-fn (dec 1))
;; => false
(called? my-fn (my-fn 1))
;; => true
有没有一种方法可以在不分解堆栈的情况下调用原始实现?我在第一个let
中尝试过这样做,但最终导致ClassCastException clojure.lang.Cons不能转换为java.util.concurrent.Future
(let [flag (gensym `flag)
orig-f# (deref f)] ... )
我不确定您的目标是自己编写这样的宏,还是可以使用现有的库 我建议您不仅可以验证传递给这些函数的参数,还可以验证传递给这些函数的参数:
(defn some-fn [n]
(inc n))
(deftest test-instumenting
(instrumenting [some-fn]
(is (= 43 (some-fn 42)))
(verify-called-once-with-args some-fn 42)))
魔术师还支持模拟和存根,以将您的测试与外部资源隔离
如果你真的想自己实现它,我会遵循魔术师的实现,它促进了一些好的实践
宏应该只是一个很薄的语法糖层,使用函数完成主要工作。创建包装函数,该函数将记录是否调用了原始函数。通过使用包装函数并单独存储计数,您可以在以后对其进行检查,因此您可以使原始函数不变地工作并返回实际结果
(def call-counts (atom {}))
(defn instrumented-fn [original-fn]
(let [wrapping-fn (fn this [& args]
(swap! call-counts
update-in
[original-fn]
inc)
(apply original-fn args))]
(swap! call-counts assoc original-fn 0)
wrapping-fn))
(defn have-been-called?
[original-fn]
(pos? (@call-counts original-fn 0))
(defn reset-counters! [] (reset! call-counts {}))
通过实用程序功能设置,您可以使用宏添加精简语法层:
(defmacro instrumenting [f & body]
`(with-redefs [~f (instrumented-fn ~f)]
~@body))
(defmacro called? [f & body]
`(let [origin-fn# ~f]
(instrumenting ~f ~@body)
(have-been-called? origin-fn#)))
用法:
user=> (called? #'clojure.core/println nil #(println "hey"))
true
user=> (called? #'clojure.core/println nil #(print "hey"))
heyfalse
(defn my-fn [n] (inc n))
(have-been-called? my-fn)
;; => false
(instrumenting my-fn (dec 1))
;; => 0
(have-been-called? my-fn)
;; => false
(instrumenting my-fn (my-fn 1))
;; => 2
(have-been-called? my-fn)
;; => true
(reset-counters!)
(have-been-called? my-fn)
;; => false
(called? my-fn (dec 1))
;; => false
(called? my-fn (my-fn 1))
;; => true