2013-08-20 89 views
9

我想为我的clojure函数编写一些单元测试(我使用clojure.test,但如果需要,我可以切换到midje)。Clojure单元测试:检查函数是否被调用

我有一个功能,倒像是:

(defn GenerateNodes 
    [is-sky-blue? hot-outside? name] 
    (cond 
    (is-sky-blue? name) (generate-sky-nodes) 
    (hot-outside?) (generate-hot-nodes))) 

当单元测试这个功能,我想写下面的测试案例:

(deftest when-sky-blue-then-generate-sky-nodes 
    (let [is-sky-blue true] 
     (GenerateNodes (fn[x] println "sky nodes generated.")) 
      (is (= true Was-generate-hot-nodes-called?)) 

我怎么能断言功能generate-天空节点被称为?或不 ? 我会在C#或java中使用模拟框架,但我不知道clojure。

+4

好问题。在我看来,你正试图对声明性代码应用命令式的风格测试。你不应该描述*事情是如何工作的,而是他们做了什么,所以被调用的函数是一个不相关的细节。不过功能编程专家会确认(我不知道)。 – guillaume31

+1

@ guillaume31,我认为这是模拟和存根之间的区别。存根只是为了提供支持行为的假实现,而嘲讽也是这样做的,并且还会计帐。就我个人而言,我发现嘲笑是非常糟糕的主意,即使在面向对象的世界。在功能世界中是双重的。但它可能只是我。 – ivant

+0

@ivant Dunno。我猜测存根仍会以某种方式描述* how *,尽管如果没有他们可能无法获得高性能测试。嘲笑我个人觉得有用,不是为了微观会计,而是为了验证一个对象不会向其中的一个对等方说粗鲁(即外部协议),这使得在OO类型系统中缺乏这些协议的流畅执行。 – guillaume31

回答

8

你已经离工作的功能版本不远了。我改变了一些东西,使其更加习惯于Clojure。

以下假设产生天空节点,并产生热节点都返回一定的价值(这可以在除任何副作用他们完成),即:

(defn generate-sky-nodes 
    [] 
    (doseq [i (range 10)] (do-make-sky-node i)) 
    :sky-nodes) 

那么,你的产生节点被调整如下:

(defn generate-nodes 
    [sky-blue? hot-outside? name] 
    (cond 
    (sky-blue? name) (generate-sky-nodes) 
    (hot-outside?) (generate-hot-nodes))) 

最后,测试的功能版本:

(deftest when-sky-blue-then-generate-sky-nodes 
    (let [truthy (constantly true) 
     falsey (constantly false) 
     name nil] 
    (is (= (generate-nodes truthy falsey name) 
     :sky-nodes)) 
    (is (= (generate-nodes truthy truthy name) 
     :sky-nodes)) 
    (is (not (= (generate-nodes falsey falsey name) 
       :sky-nodes))) 
    (is (not (= (generate-nodes falsey truthy name) 
       :sky-nodes))))) 

总的想法是,你不测试它做了什么,你测试它返回的是什么。然后你安排你的代码,使得(尽可能)所有关于函数调用的事情都是它返回的。

另外一个建议是使用generate-sky-nodesgenerate-hot-nodes回到副作用最小化,其中的副作用发生的地方数进行:

(defn generate-sky-nodes 
    [] 
    (fn [] 
    (doseq [i (range 10)] (do-make-sky-node i)) 
    :sky-nodes)) 

和你的generate-nodes调用将如下所示:

(apply (generate-nodes blue-test hot-test name) []) 

或更简洁(但无可否认奇怪,如果你不太熟悉的Clojure):

((generate-nodes blue-test hot-test name)) 

(在上面的测试代码比照测试将使用该版本的工作以及)

+0

对于尚未熟悉功能范例的人来说,这是一个很好的例子。 +1 –

+0

好帖子!是否有这种模式的名称,使副作用函数返回一个“标记”来帮助测试? – camdez

9

你可以自己编写一个宏来模拟函数,并检查函数是否被调用。或者您可以使用expect-call库。

(defn check-error [a b] 
    (when (= a :bad-val) 
    (log :error b))) 

; This will pass 
(deftest check-logging 
    (with-expect-call (log [:error _]) 
    (check-error :bad-val "abc"))) 

; This will fail 
(deftest check-logging-2 
    (expect-call (log [:error _]) 
    (check-error :good-val "abc"))) 
3

使用Midjecheckables

(unfinished is-sky-blue? hot-outside?) 
(facts "about GenerateNodes" 
    (fact "when the sky is blue then sky nodes are generated" 
    (GenerateNodes is-sky-blue? hot-outside? ..name..) => ..sky-nodes.. 
    (provided 
     (is-sky-blue? ..name..) => true 
     (generate-sky-nodes) => ..sky-nodes.. 
     (generate-hot-nodes) => irrelevant :times 0))) 
0

您可以使用mock-clj

(require ['mock-clj.core :as 'm]) 

(deftest when-sky-blue-then-generate-sky-nodes 
    (m/with-mock [is-sky-blue? true 
       generate-sky-nodes nil] 
    (GenerateNodes (fn[x] println "sky nodes generated.") ... ...) 
    (is (m/called? generate-sky-nodes)))) 
相关问题