你怎么能在clojure中模拟测试中的宏?

时间:2017-01-23 18:57:43

标签: unit-testing clojure macros

我想在命名空间中模拟一个宏。

例如,clojure.tools.logging/error

我试了with-redefs没有运气

(def logged false)

(defmacro testerror
  {:arglists '([message & more] [throwable message & more])}
  [& args]
  `(def logged true))

(deftest foo
 ...
 (with-redefs
      [log/error testerror]
   ...

这给出了这个错误: CompilerException java.lang.RuntimeException: Can't take value of a macro

2 个答案:

答案 0 :(得分:5)

你不能这样做。宏的重点在于,在编译代码时它们会被扩展,之后它们就会消失。包含对宏的调用的原始代码是不可恢复的。你不能在运行时追溯重新定义一个宏:你已经太晚了。

另一种方法,如果您希望具有可交换日志记录实现,则可以使用Component之类的内容来进行依赖项注入,并根据您是运行测试还是运行实际程序来使用不同的日志记录组件。可以说这有点笨拙,也许有一种更简单的方法,但我不知道。

答案 1 :(得分:2)

Amalloy为您提供了关于如何模拟宏的直接问题的答案 - 您不能。

但是,您可以使用其他解决方案解决问题(比将整个应用程序移动到组件依赖项注入更简单)。让我建议两个替代实现(遗憾的是,不是非常简单,但仍然比使用组件简单)。

模拟记录宏

调用的函数

您无法模拟宏,但可以模拟在记录宏扩展时将使用的函数。

(require '[clojure.tools.logging :as log])
(require '[clojure.pprint :refer [pprint]])

(pprint (macroexpand `(log/error (Exception. "Boom") "There was a failure")))

给出:

(let*
 [logger__739__auto__
  (clojure.tools.logging.impl/get-logger
   clojure.tools.logging/*logger-factory*
   #object[clojure.lang.Namespace 0x2c50fafc "boot.user"])]
 (if
  (clojure.tools.logging.impl/enabled? logger__739__auto__ :error)
  (clojure.core/let
   [x__740__auto__ (java.lang.Exception. "Boom")]
   (if
    (clojure.core/instance? java.lang.Throwable x__740__auto__)
    (clojure.tools.logging/log*
     logger__739__auto__
     :error
     x__740__auto__
     (clojure.core/print-str "There was a failure"))
    (clojure.tools.logging/log*
     logger__739__auto__
     :error
     nil
     (clojure.core/print-str x__740__auto__ "There was a failure"))))))

如您所见,执行实际日志记录的功能(如果启用了给定级别)是使用clojure.tools.logging/log*函数完成的。

我们可以嘲笑它并编写我们的测试:

(require '[clojure.test :refer :all])

(def log-messages (atom []))

(defn log*-mock [logger level throwable message]
  (swap! log-messages conj {:logger logger :level level :throwable throwable :message message}))

(with-redefs [clojure.tools.logging/log* log*-mock]
  (let [ex (Exception. "Boom")]
    (log/error ex "There was a failure")
    (let [logged (first @log-messages)]
      (is (= :error (:level logged)))
      (is (= "There was a failure!" (:message logged)))
      (is (= ex (:throwable logged))))))

使用您的日志库API来收集和检查日志消息

您的日志库API可能会提供允许您插入测试以收集和断言日志记录事件的功能。例如,使用java.util.logging,您可以编写自己的Handler实现,以收集所有记录的日志记录和add it to a specific (or root) logger