2013-08-20 4 views
9

Я пытаюсь написать некоторые модульные тесты для моей функции clojure (я использую clojure.test, но при необходимости я могу переключиться на среднюю).Clojure Unit testing: проверьте, вызвана ли функция

У меня есть функция, которая читается как:

(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

Хороший вопрос. Мне кажется, вы пытаетесь применить обязательное тестирование стиля к декларативному коду.Вы не должны описывать *, как все работает, но * что * они делают, поэтому вызываемая функция является IMO неуместной деталью. Подтверждаются эксперты по функциональному программированию (что я не знаю). – guillaume31

+1

@ guillaume31, я думаю, что это разница между mocks и stub. Заготовки там только для того, чтобы обеспечить поддельные реализации поддерживающего поведения, в то время как макеты делают это, а также делают учет. Лично я считаю, что издевательства - исключительно плохая идея, даже в мире ОО. Совсем так в функциональном мире. Но это может быть только я. – ivant

+0

@ivant Не знаю. Я думаю, что заглушки все еще каким-то образом описывают * как *, хотя вы, вероятно, не можете обойтись без них, чтобы получить тесты на выполнение. Моски, которые я лично считаю полезными, а не для микро-учета, но для проверки того, что объект не говорит грубо (т. Е. Внешний протокол) с одним из своих сверстников, делая из-за отсутствия свободного применения этих протоколов в системах типов 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-nodes и generate-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

Midje Использование «s checkables:

(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))))