Testování v Clojure

Ačkoliv Rich Hickey říká, že nepíše unit testy, přece jenom do Clojure zahrnul “framework” pro unit testy. Já sám, naopak, jsem již léty TDD infikován a testy píšu rád. Takže, jak se to dělá v Clojure? Nejprve natáhneme knihovnu clojure.test:

(ns my-tests (:use clojure.test))
; nil

Aserce (tvrzení)

Základem testů je makro is:

(is (= 2 (+ 1 1)))
; true
(is (odd? 1))
; true
(is (even? 1) "truth about one")
; 
; FAIL in clojure.lang.PersistentList$EmptyList@1 (NO_SOURCE_FILE:30)
; truth about one
; expected: (even? 1)
;   actual: (not (even? 1))
; false

Více assertions/parametrů se dá testovat pomocí makra are:

(are [x y] (= 42 (+ x y))
     21 21
     20 22
     42 0)
; true

Vyhození výjimky se dá otestovat pomocí funkce thrown?:

(is (thrown? ArithmeticException (/ 42 0)))
; #<ArithmeticException java.lang.ArithmeticException: Divide by zero>
(is (thrown? ArithmeticException (/ 42 42)))
; 
; FAIL in clojure.lang.PersistentList$EmptyList@1 (NO_SOURCE_FILE:48)
; expected: (thrown? ArithmeticException (/ 42 42))
;   actual: nil
; nil

Definice testů

OK, to byly tvrzení (assertions). Jak definuji test? Jedna z možností je makro with-test:

(with-test
    (defn add [x y]
        (+ x y))
    (is (= 42 (add 21 21)))
    (is (= 42 (add 20 22)))
    (is (= 42 (add 42 0))))

Druhou možností je použít makro deftest. Jeho výhodou je, že vytváří funkci (v našem případě ultimate-addition), kterou lze zavolat z libovolného namespace. To umožňuje mít testy v samostatném souboru tak, jak jsme z unit testů zvyklí.

(deftest ultimate-addition
    (is (= 42 (add 21 21)))
    (is (= 42 (add 20 22)))
    (is (= 42 (add 42 0))))

Spuštění testů

Máme definované dva testy (a šest asercí). Jak je spustíme? Testy v aktuálním namespace odpálíme funkcí run-tests:

(run-tests)
;
; Testing my-tests
;
; Ran 2 tests containing 6 assertions.
; 0 failures, 0 errors.
; {:type :summary, :pass 6, :test 2, :error 0, :fail 0}

Testy v jiném namespace spustíme obdobně:

(run-tests 'some.namespace 'some.other.namespace)

Automatizace testů

Tak. Konec srandiček! Velký kluci buildujou (a spouští testy) Leiningenem. Prvně vytvoříme projekt příkazem lein new lein-tests:

Vytvoření a layout projektu

Vytvoření a layout projektu

Test first! Napíšeme testy (soubor core.clj v adresáři test):

(ns lein-tests.test.core
  (:use [lein-tests.core])
  (:use [clojure.test]))

(deftest split-line-test
  (is (= ["foo" "bar"]
         (split-line "foo;bar" #";")))
  (is (= ["foo" "bar"]
         (split-line "foo,bar" #", ?")))
  (is (= ["foo" "bar"]
         (split-line "foo, bar" #", ?"))))

(deftest get-phone-test
  (is (= "123456789"
         (get-phone "123456789;Guido")))
  (is (= ""
         (get-phone ";Guido"))))

A napíšeme (testované) funkce (soubor core.clj v adresáři src):

(ns lein-tests.core
  (:use [clojure.string :only (split)]))

(defn split-line [line separator]
  (split line separator))

(defn get-phone [line]
  (first (split-line line #";")))

No a na závěr testy spustíme příkazem lein test. Nádhera!

Spuštění testů

Spuštění testů