2013-08-20 128 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)))) 
相關問題