Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/clojure/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Clojure宏检查是否调用了函数_Clojure_Macros_Jvm - Fatal编程技术网

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)

我在一个月前开始使用Clojure,为了进行集成测试,我开发了一个小宏来检查一个函数是否是从其他函数调用的。宏当前看起来如下所示:

(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