Leiningen: nástroj pro správu projektů napsaných v Clojure (3)

Pavel Tišnovský 26. 2. 2015

Ve třetí části článku o nástroji Leiningen určeného pro správu projektů v programovacím jazyku Clojure se seznámíme se základy problematiky tvorby testů (unit tests). Taktéž se zmíníme o přídavném modulu nástroje Leiningen, který je možné použít pro export výsledků testování do formátu kompatibilního s JUnit.

Obsah

1. Leiningen: nástroj pro správu projektů napsaných v Clojure (3)

2. Načtení knihovny clojure.test

3. Využití makra is v interaktivní smyčce REPL

4. Test, zda byla vyhozena výjimka specifikovaného typu

5. Nástroj Leiningen a jednotkové testy

6. Vytvoření nového projektu obsahujícího funkci pro výpočet faktoriálu

7. Úprava jednotkového testu a následné spuštění jednotkového testu

8. Výsledek běhu jednotkového testu v případě, že je testovaná funkce chybná

9. Jednotkové testy s výstupem kompatibilním s JUnit

10. Vytvoření druhého demonstračního příkladu factorial2

11. Spuštění jednotkového testu a konverze výsledku do formátu kompatibilního s JUnit

12. Odkazy na Internetu

1. Leiningen: nástroj pro správu projektů napsaných v Clojure (3)

Ve třetí části článku o nástroji Leiningen, který při správném použití dokáže zjednodušit práci s projekty napsanými v programovacím jazyku Clojure, se nejdříve seznámíme se základy tvorby jednotkových testů (unit tests). Nejprve si vysvětlíme, jakým způsobem se jednotkové testy mohou psát s využitím makra is deklarovaného ve standardní knihovně clojure.test. Následně vytvoříme malý projekt obsahující jeden jednotkový test a posléze si ukážeme přídavný modul, který je možné v případě potřeby využít pro vytvoření exportů s výsledky jednotkových testů, které jsou kompatibilní s formátem používaným knihovnou JUnit (ale i mnoha dalšími podobnými nástroji). Díky použití tohoto přídavného modulu lze projekt napsaný v Clojure velmi jednoduše testovat například na Jenkinsu/Hudsonu a využívat například již dostupné nástroje pro tvoru grafů s výsledky testů apod.

2. Načtení knihovny clojure.test

Součástí standardních knihoven jazyka Clojure je mj. i knihovna nazvaná clojure.test obsahující makra a funkce, které je možné použít pro tvorbu jednotkových testů. Nejprve si ukážeme, jak se používá základní makro is, a to přímo s využitím interaktivní smyčky REPL – ostatně programovací jazyk Clojure je nejlepší se učit právě se spuštěnou smyčkou REPL. Předpokládejme, že nástroj Leiningen již byl úspěšně nainstalován podle návodu zmíněného v první části tohoto článku. To mj. znamená, že lze (z libovolného adresáře) spustit i interaktivní smyčku REPL, a to použitím následujícího příkazu:

lein repl

Po zadání tohoto příkazu by se na konzoli/terminálu mělo spustit interaktivní rozhraní jazyka Clojure (z vypsané zprávy je mj. patrné, že už je načase provést update balíčku OpenJDK :-):

nREPL server started on port 38398 on host 127.0.0.1 - nrepl://127.0.0.1:38398
REPL-y 0.3.5, nREPL 0.2.6
Clojure 1.6.0
OpenJDK 64-Bit Server VM 1.7.0_71-mockbuild_2014_10_15_17_02-b00
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
    Exit: Control+D or (exit) or (quit)
 Results: Stored in vars *1, *2, *3, an exception in *e
 
user=>

Funkce a makra z knihovny (a současně i ze jmenného prostoru) clojure.test se načetly automaticky, ovšem při volání těchto funkcí popř. maker by se musel použít plný název včetně jmenného prostoru, například:

user=> (clojure.test/is)

Aby byla naše práce v REPL jednodušší, je vhodné zabezpečit přímý přístup ke všem funkcím a makrům ze jmenného prostoru clojure.test, a to následujícím příkazem:

user=> (use 'clojure.test)
nil

Předchozí příkaz by měl pouze vypsat návratovou hodnotu, tj. nil (samotné provedení příkazu tedy proběhne „tiše“). Pokud se vypíše chybové hlášení, což je mimochodem dosti nepravděpodobné, znamená to problém v samotné instalaci Clojure, popř. nekorektně nastavenou proměnnou CLASSPATH. Při zjišťování, kde nastala chyba, se budou hodit příkazy:

; otestování existence jmenného prostoru
user=> (find-ns 'clojure.test)
#<Namespace clojure.test>
 
; pokud se vrátí nil, jmenný prostor nebyl načten
; získání mapy všech veřejných symbolů ze jmenného prostoru
user=> (ns-publics 'clojure.test)
 
{are #'clojure.test/are, test-all-vars #'clojure.test/test-all-vars, test-var
#'clojure.test/test-var, do-report #'clojure.test/do-report, run-all-tests
#'clojure.test/run-all-tests, assert-any #'clojure.test/assert-any,
testing-contexts-str #'clojure.test/testing-contexts-str, file-position
#'clojure.test/file-position, testing #'clojure.test/testing, join-fixtures
#'clojure.test/join-fixtures, set-test #'clojure.test/set-test,
get-possibly-unbound-var #'clojure.test/get-possibly-unbound-var, assert-expr
#'clojure.test/assert-expr, report #'clojure.test/report, compose-fixtures
#'clojure.test/compose-fixtures, with-test #'clojure.test/with-test,
*stack-trace-depth* #'clojure.test/*stack-trace-depth*, is #'clojure.test/is,
*report-counters* #'clojure.test/*report-counters*, *load-tests*
#'clojure.test/*load-tests*, deftest #'clojure.test/deftest, assert-predicate
#'clojure.test/assert-predicate, with-test-out #'clojure.test/with-test-out,
function? #'clojure.test/function?, deftest- #'clojure.test/deftest-, test-vars
#'clojure.test/test-vars, try-expr #'clojure.test/try-expr, successful?
#'clojure.test/successful?, use-fixtures #'clojure.test/use-fixtures,
inc-report-counter #'clojure.test/inc-report-counter, testing-vars-str
#'clojure.test/testing-vars-str, *testing-contexts*
#'clojure.test/*testing-contexts*, test-ns #'clojure.test/test-ns, run-tests
#'clojure.test/run-tests, *testing-vars* #'clojure.test/*testing-vars*,
*test-out* #'clojure.test/*test-out*, *initial-report-counters*
#'clojure.test/*initial-report-counters*}
; získání setříděných jmen všech veřejných symbolů ze jmenného prostoru
user=> (pprint (sort (keys (ns-publics 'clojure.test))))
 
(*initial-report-counters*
 *load-tests*
 *report-counters*
 *stack-trace-depth*
 *test-out*
 *testing-contexts*
 *testing-vars*
 are
 assert-any
 assert-expr
 assert-predicate
 compose-fixtures
 deftest
 deftest-
 do-report
 file-position
 function?
 get-possibly-unbound-var
 inc-report-counter
 is
 join-fixtures
 report
 run-all-tests
 run-tests
 set-test
 successful?
 test-all-vars
 test-ns
 test-var
 test-vars
 testing
 testing-contexts-str
 testing-vars-str
 try-expr
 use-fixtures
 with-test
 with-test-out)

3. Využití makra is v interaktivní smyčce REPL

Základem pro psaní jednotkových testů je makro nazvané jednoduše is, takže se nejprve podívejme na to, co o tomto makru říká dokumentace. Prohlížení dokumentace slouží makro doc, kterému se jako parametr předá jméno funkce, makra či symbolu, jehož význam potřebujeme zjistit:

user=> (doc is)
-------------------------
clojure.test/is
([form] [form msg])
Macro
  Generic assertion macro.  'form' is any predicate test.
  'msg' is an optional message to attach to the assertion.
 
  Example: (is (= 4 (+ 2 2)) "Two plus two should be 4")
 
  Special forms:
 
  (is (thrown? c body)) checks that an instance of c is thrown from
  body, fails if not; then returns the thing thrown.
 
  (is (thrown-with-msg? c re body)) checks that an instance of c is
  thrown AND that the message on the exception matches (with
  re-find) the regular expression re.
nil

Vidíme, že tomuto makru lze předat takzvaný predikát a popř. i textovou zprávu. Predikát je použit ve dvou významech – po svém vyhodnocení se zjišťuje výsledná hodnota a pokud není predikát splněn, vypíše se chybové hlášení obsahující jak původní znění predikátu, tak i aktuální (odlišná) hodnota vzniklá vyhodnocením. Mimochodem: právě proto, že se vypisuje text predikátu, nemůže být is implementováno pomocí funkce, ale bylo nutné použít makro. Chování makra is si můžeme snadno odzkoušet:

user=> (is true)
true
user=> (is (= (+ 1 1) 2))
true
user=> (is (= (inc 1) 2))
true
user=> (is (nil? nil))
true
user=> (is (seq? '(1 2 3)))
true
user=> (is (fn? println))
true

Co se stane ve chvíli, kdy není predikát splněn, lze opět snadno odzkoušet:

user=> (is (= 1 2))
 
FAIL in clojure.lang.PersistentList$EmptyList@1 (NO_SOURCE_FILE:1)
expected: (= 1 2)
  actual: (not (= 1 2))
false
user=> (is (nil? "ja nejsem nil"))
 
FAIL in clojure.lang.PersistentList$EmptyList@1 (NO_SOURCE_FILE:1)
expected: (nil? "ja nejsem nil")
  actual: (not (nil? "ja nejsem nil"))
false
user=> (is (= (inc 1) 3))
 
FAIL in clojure.lang.PersistentList$EmptyList@1 (NO_SOURCE_FILE:1)
expected: (= (inc 1) 3)
  actual: (not (= 2 3))
false
user=> (is (fn? true))
 
FAIL in clojure.lang.PersistentList$EmptyList@1 (NO_SOURCE_FILE:1)
expected: (fn? true)
  actual: (not (fn? true))
false

Řádek začínající slovem FAIL jen naznačuje, že makro is spouštíme z interaktivní konzole a nikoli ze zdrojového kódu (kde by bylo známé jak jméno zdrojového souboru, tak i číslo řádku, na němž je makro is použito). Tento nedostatek se nijak neprojeví při testování reálných aplikací.

4. Test, zda byla vyhozena výjimka specifikovaného typu

V nápovědě zobrazené k makru is je mj. popsána i speciální forma použití tohoto makra:

(is (thrown? c body))

Tuto formu je možné použít pro otestování, zda zavolání nějaké funkce vyvolá výjimky určitého typu (typ je určen třídou c). Podívejme se na velmi jednoduchý příklad. Tím je dělení nulou, které vede k vyhození výjimky typu ArithmeticException. Ostatně můžeme se sami přesvědčit, zda je to pravda:

user=> (/ 42 0)
 
ArithmeticException Divide by zero  clojure.lang.Numbers.divide (Numbers.java:156)

Výjimka skutečně byla vyhozena, takže můžeme zkusit, co se stane ve chvíli, kdy se využije výše uvedená speciální forma volání makra is:

user=> (is (thrown? ArithmeticException (/ 42 0)))
#<ArithmeticException java.lang.ArithmeticException: Divide by zero<

Výsledkem volání je instance třídy ArithmeticException. Opět se můžeme snadno přesvědčit, že je to pravda:

user=> (def result (is (thrown? ArithmeticException (/ 42 0))))
#'user/result
user=> result
#<ArithmeticException java.lang.ArithmeticException: Divide by zero<
user=> (type result)
java.lang.ArithmeticException

Ve chvíli, kdy se použije format (is (thrown? …)) a k vyhození výjimky nedojde, vypíše makro is následující zprávu:

user=> (is (thrown? ArithmeticException (/ 42 1)))
 
FAIL in clojure.lang.PersistentList$EmptyList@1 (NO_SOURCE_FILE:1)
expected: (thrown? ArithmeticException (/ 42 1))
  actual: nil
nil

Můžeme zkusit i poněkud složitější příklad – otestování chování funkce nth při pokusu o získání prvku z vektoru, přičemž index bude záporný (či větší než délka vektoru):

; vytvoření vektoru
user=> (def data [1 2 3])
#'user/data
 
; prvek s indexem 1 existuje (podle očekávání)
user=> (nth data 1)
2
 
; ovšem prvek s indexem -1 již neexistuje (opět podle očekávání)
user=> (nth data -1)
 
IndexOutOfBoundsException   clojure.lang.PersistentVector.arrayFor (PersistentVector.java:107)
 
; (v mnoha případech je vhodnější namísto (nth) použít (get)

Opět můžeme použít makro is pro zjištění, zda k výjimce dojde či nikoli:

user=> (is (thrown? IndexOutOfBoundsException (nth data -1)))
#<IndexOutOfBoundsException java.lang.IndexOutOfBoundsException>
 
user=> (is (thrown? IndexOutOfBoundsException (nth data 1)))
 
FAIL in clojure.lang.PersistentList$EmptyList@1 (form-init3748557964393682041.clj:1)
expected: (thrown? IndexOutOfBoundsException (nth data 1))
  actual: nil
nil

Základní informace o chování makra is již nyní známe, takže můžeme opustit smyčku REPL:

user=> (quit)

5. Nástroj Leiningen a jednotkové testy

V předchozích dvou kapitolách jsme si ukázali některé možnosti makra is, takže si již můžeme říci, jakým způsobem je spouštění jednotkových testů podporováno v nástroji Leiningen. Již v první a druhé části tohoto článku jsme si popisovali strukturu nově vytvořeného projektu. Připomeňme si, že projekt je tvořen adresářem obsahujícím především soubor project.clj a dále podadresář src, v němž jsou umístěny zdrojové kódy aplikace. Kromě toho však v projektu nalezneme i podadresář nazvaný test, jehož účel je jednoduchý – v tomto adresáři jsou uloženy jednotkové testy a to (většinou) strukturované tak, že každému modulu uloženému v adresáři src odpovídá jeden soubor s jednotkovými testy uložený v adresáři test. Pokud tedy bude aplikace obsahovat tři moduly nazvané core, module1module2, může struktura projektu vypadat následovně:

.
├── doc
│   └── intro.md
├── LICENSE
├── project.clj
├── README.md
├── resources
├── src
│   └── factorial
│       ├── module1.clj
│       ├── module2.clj
│       └── core.clj
├── test
│   └── factorial
│       ├── module1_test.clj
│       ├── module2_test.clj
│       └── core_test.clj

Všechny jednotkové testy se spouští příkazem:

lein test

6. Vytvoření nového projektu obsahujícího funkci pro výpočet faktoriálu

Pro zjištění, jakým způsobem se s jednotkovými testy pracuje prakticky, si vytvoříme nový projekt, v němž bude implementována funkce pro výpočet faktoriálu:

lein new app factorial

Na rozdíl od předchozích částí tohoto článku nebudeme měnit nastavení projektu, tj. obsah souboru project.clj. Upravíme ovšem zdrojový kód projektu, tedy soubor src/factorial/core.clj následujícím způsobem:

(ns factorial.core
    (:gen-class))
 
; funkce faktorial obsahuje i test na zaporne hodnoty
(defn factorial
    [n]
    (if (neg? n)
        (throw (IllegalArgumentException. "negative numbers are not supported!"))
        (apply * (range 1 (inc n)))))
 
; otestujeme funkci faktorial
(defn -main
    [& args]
    (doseq [i (range 0 10)]
        (println i "! = " (factorial i))))

Po spuštění této aplikace příkazem lein run by se na standardní výstup měly vypsat faktoriály pro hodnoty nula až devět:

lein run
 
0 ! =  1
1 ! =  1
2 ! =  2
3 ! =  6
4 ! =  24
5 ! =  120
6 ! =  720
7 ! =  5040
8 ! =  40320
9 ! =  362880

7. Úprava jednotkového testu a následné spuštění jednotkového testu

Druhým krokem bude příprava jednotkového testu, tedy konkrétně souboru test/factorial/core_test.clj. V tomto souboru budou deklarovány dva testy:

(ns factorial.core-test
  (:require [clojure.test :refer :all]
            [factorial.core :refer :all]))
 
(deftest factorial-test
    (testing "Factorial"
        (is ( = (factorial 0)   1) "beginning")
        (is ( = (factorial 1)   1) "beginning")
        (is ( = (factorial 2)   (* 1 2)) "still easy")
        (is ( = (factorial 5)   (* 1 2 3 4 5)) "5!")
        (is ( = (factorial 6)   720) "6!")))
 
(deftest exception-test
    (testing "If factorial throws exception"
        (is (thrown? IllegalArgumentException (factorial -1)))
        (is (thrown? IllegalArgumentException (factorial -2)))
        (is (thrown? IllegalArgumentException (factorial -100)))))

Povšimněte si, že jednotlivé testy jsou deklarovány pomocí deftest, což je funkce sdružující více jednotkových testů. Uvnitř deftest lze použít makro testing zajišťující další úroveň sdružování, není to však nutné (já zde makro používám pro zvýšení čitelnosti). Poté následují již jednotlivé testy používající makro is, s nímž jsme se již seznámili v předchozích kapitolách. Za povšimnutí stojí i způsob použití makra ns na začátku testu, především pak třetí řádek, který zajistí nahrání všech testovaných funkcí (to se neprovádí automaticky).

Testy lze spustit z adresáře projektu:

lein test

Na standardní výstup by se měly vypsat následující zprávy:

lein test factorial.core-test
 
Ran 2 tests containing 8 assertions.
0 failures, 0 errors.

8. Výsledek běhu jednotkového testu v případě, že je testovaná funkce chybná

V dalším kroku se pokusme funkci pro výpočet faktoriálu „rozbít“ takovým způsobem, aby vracela nesprávné výsledky. Namísto korektního kódu:

(defn factorial
    [n]
    (if (neg? n)
        (throw (IllegalArgumentException. "negative numbers are not supported!"))
        (apply * (range 1 (inc n)))))

se bude provádět tento kód:

(defn factorial
    [n]
    (if (neg? n)
        (throw (IllegalArgumentException. "negative numbers are not supported!"))
        (apply * (range 1 n))))

Aplikaci samozřejmě půjde spustit, ovšem bude vypisovat nekorektní výsledky:

lein run
 
0 ! =  1
1 ! =  1
2 ! =  1
3 ! =  2
4 ! =  6
5 ! =  24
6 ! =  120
7 ! =  720
8 ! =  5040
9 ! =  40320

Co se nyní stane při spuštění jednotkových testů?

lein test
 
lein test factorial.core-test
 
lein test :only factorial.core-test/factorial-test
 
FAIL in (factorial-test) (core_test.clj:9)
Factorial
still easy
expected: (= (factorial 2) (* 1 2))
  actual: (not (= 1 2))
 
lein test :only factorial.core-test/factorial-test
 
FAIL in (factorial-test) (core_test.clj:10)
Factorial
5!
expected: (= (factorial 5) (* 1 2 3 4 5))
  actual: (not (= 24 120))
 
lein test :only factorial.core-test/factorial-test
 
FAIL in (factorial-test) (core_test.clj:11)
Factorial
6!
expected: (= (factorial 6) 720)
  actual: (not (= 120 720))
 
Ran 2 tests containing 8 assertions.
3 failures, 0 errors.
Tests failed.

Vidíme, že se chyba v implementaci ihned projevila ve výsledcích testů.

9. Jednotkové testy s výstupem kompatibilním s JUnit

Příkazem lein run je sice možné spustit jednotkové testy a získat čitelný výstup, tj. informaci o tom, kolik testů bylo spuštěno, kolik testů proběhlo v pořádku a které testy naopak našly v aplikaci chybu, ovšem výstupní formát je poněkud neobvyklý. Ve světě Javy (a vlastně i mimo tento svět) se ustálilo použití XML formátu kompatibilního s nástrojem JUnit. Tento formát dokážou zpracovat jak mnohá integrovaná vývojová prostředí, tak i například několik přídavných modulů pro systém Jenkins popř. Hudson. Tyto moduly dokážou například vytvářet grafy s regresemi atd., takže by bylo vhodné nějakým způsobem upravit Leiningen takovým způsobem, aby formát JUnitu podporoval. To je samozřejmě možné, a to především díky velké rozšiřitelnosti Leiningenu o další moduly. Modul, který budeme potřebovat, se jmenuje jednoduše test2junit a v následujících dvou kapitolách si ukážeme jeho základní použití.

10. Vytvoření druhého demonstračního příkladu factorial2

Vytvoříme si nový demonstrační příklad nazvaný jednoduše factorial2:

lein new app factorial2

Následně musíme upravit soubor project.clj takovým způsobem, že se do něj přidá informace o modulu rozšiřujícím možnosti Leiningenu. Nový řádek je zvýrazněn:

(defproject factorial2 "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.6.0"]]
  :main ^:skip-aot factorial2.core
  :target-path "target/%s"
  :plugins [[test2junit "1.1.0"]]
  :profiles {:uberjar {:aot :all}})

Následně je nutné spustit následující příkaz, který zajistí stažení nového modulu a popř. i všech knihoven, na nichž tento modul závisí:

lein deps
 
Retrieving test2junit/test2junit/1.1.0/test2junit-1.1.0.pom from clojars
Retrieving test2junit/test2junit/1.1.0/test2junit-1.1.0.jar from clojars

Obsah souborů src/factorial2/core.cljtest/factorial2/core_test.clj bude shodný s předchozím příkladem, samozřejmě až na jinak pojmenovaný jmenný prostor (je přidána dvojka):

(ns factorial2.core
    (:gen-class))
 
; funkce faktorial obsahuje i test na zaporne hodnoty
(defn factorial
    [n]
    (if (neg? n)
        (throw (IllegalArgumentException. "negative numbers are not supported!"))
        (apply * (range 1 (inc n)))))
 
; otestujeme funkci faktorial
(defn -main
    [& args]
    (doseq [i (range 0 10)]
        (println i "! = " (factorial i))))
(ns factorial2.core-test
  (:require [clojure.test :refer :all]
            [factorial2.core :refer :all]))
 
(deftest factorial-test
    (testing "Factorial"
        (is ( = (factorial 0)   1) "beginning")
        (is ( = (factorial 1)   1) "beginning")
        (is ( = (factorial 2)   (* 1 2)) "still easy")
        (is ( = (factorial 5)   (* 1 2 3 4 5)) "5!")
        (is ( = (factorial 6)   720) "6!")))
 
(deftest exception-test
    (testing "If factorial throws exception"
        (is (thrown? IllegalArgumentException (factorial -1)))
        (is (thrown? IllegalArgumentException (factorial -2)))
        (is (thrown? IllegalArgumentException (factorial -100)))))

11. Spuštění jednotkového testu a konverze výsledku do formátu kompatibilního s JUnit

Nyní nastává zajímavý okamžik – spustíme totiž nástroj Leiningen s novým jménem úkolu (task). To je možné, protože Leiningen byl díky úpravě souboru project.clj rozšířen o novou funkcionalitu:

lein test2junit

Na standardní výstup se vypíšou následující informace (v některých případech se však ještě stáhnou zbývající knihovny, na nichž dokončení zvoleného úkolu závisí):

Using test2junit version: 1.1.0
Running Tests...
Writing output to: test2junit
Creating default build.xml file.
 
Testing: factorial2.core-test
 
Ran 2 tests containing 8 assertions.
0 failures, 0 errors.

Výsledkem běhu tohoto nového tasku je soubor build.xml a především pak adresářová struktura test2unit obsahující soubor s cestou test2unit/xml/factorial2.core-test.xml. Podívejme se na obsah tohoto souboru:

<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="factorial2.core-test" errors="0" failures="0" tests="2" time="0.0142" timestamp="2015-02-21_20:56:31+0100">
    <testcase name="factorial-test" classname="factorial2.core-test" time="0.0044">
    </testcase>
    <testcase name="exception-test" classname="factorial2.core-test" time="0.0013">
    </testcase>
</testsuite>

Vidíme, že jsou zde uloženy informace jak o jménu spuštěných testů, tak i o době běhu a čase spuštění.

Pokud uděláme ve zdrojovém kódu aplikace záměrnou chybu – vynechání volání funkce inc – bude výsledek běhu testů odlišný:

widgety

lein test2junit
 
Using test2junit version: 1.1.0
Running Tests...
Writing output to: test2junit
 
Testing: factorial2.core-test
 
Ran 2 tests containing 8 assertions.
3 failures, 0 errors.
Tests failed.
Tests failed.

A odlišovat se samozřejmě bude i výstupní XML soubor:o

<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="factorial2.core-test" errors="0" failures="3" tests="2" time="0.0232" timestamp="2015-02-21_20:59:03+0100">
    <testcase name="factorial-test" classname="factorial2.core-test" time="0.0168">
    <failure message="still easy">still easy
expected: (= (factorial 2) (* 1 2))
  actual: (not (= 1 2))
      at: AFn.java:18</failure>
    <failure message="5!">5!
expected: (= (factorial 5) (* 1 2 3 4 5))
  actual: (not (= 24 120))
      at: AFn.java:18</failure>
    <failure message="6!">6!
expected: (= (factorial 6) 720)
  actual: (not (= 120 720))
      at: AFn.java:18</failure>
    </testcase>
    <testcase name="exception-test" classname="factorial2.core-test" time="0.0011">
    </testcase>
</testsuite>

Ve čtvrté části tohoto článku se budeme věnovat problematice tvorby webových aplikací s využitím knihoven RingHiccup.

12. Odkazy na Internetu

  1. Leiningen: nástroj pro správu projektů napsaných v Clojure
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure/
  2. Leiningen: nástroj pro správu projektů napsaných v Clojure (2)
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-2/
  3. Unit Testing in Clojure
    http://nakkaya.com/2009/11/18/unit-testing-in-clojure/
  4. Testing in Clojure (Part-1: Unit testing)
    http://blog.knoldus.com/2014/03/22/tes­ting-in-clojure-part-1-unit-testing/
  5. API for clojure.test – Clojure v1.6 (stable)
    https://clojure.github.io/clo­jure/clojure.test-api.html
  6. Leiningen: úvodní stránka
    http://leiningen.org/
  7. Leiningen: Git repository
    https://github.com/techno­mancy/leiningen
  8. leiningen-win-installer
    http://leiningen-win-installer.djpowell.net/
  9. Clojure 1: Úvod
    http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm/
  10. Clojure 2: Symboly, kolekce atd.
    http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-2-cast/
  11. Clojure 3: Funkcionální programování
    http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-3-cast-funkcionalni-programovani/
  12. Clojure 4: Kolekce, sekvence a lazy sekvence
    http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-4-cast-kolekce-sekvence-a-lazy-sekvence/
  13. Clojure 5: Sekvence, lazy sekvence a paralelní programy
    http://www.root.cz/clanky/clojure-a-bezpecne-aplikace-pro-jvm-sekvence-lazy-sekvence-a-paralelni-programy/
  14. Clojure 6: Podpora pro paralelní programování
    http://www.root.cz/clanky/programovaci-jazyk-clojure-6-futures-nejsou-jen-financni-derivaty/
  15. Clojure 7: Další funkce pro paralelní programování
    http://www.root.cz/clanky/programovaci-jazyk-clojure-7-dalsi-podpurne-prostredky-pro-paralelni-programovani/
  16. Clojure 8: Identity, stavy, neměnné hodnoty a reference
    http://www.root.cz/clanky/programovaci-jazyk-clojure-8-identity-stavy-nemenne-hodnoty-a-referencni-typy/
  17. Clojure 9: Validátory, pozorovatelé a kooperace s Javou
    http://www.root.cz/clanky/programovaci-jazyk-clojure-9-validatory-pozorovatele-a-kooperace-mezi-clojure-a-javou/
  18. Clojure 10: Kooperace mezi Clojure a Javou
    http://www.root.cz/clanky/programovaci-jazyk-clojure-10-kooperace-mezi-clojure-a-javou-pokracovani/
  19. Clojure 11: Generátorová notace seznamu/list comprehension
    http://www.root.cz/clanky/programovaci-jazyk-clojure-11-generatorova-notace-seznamu-list-comprehension/
  20. Clojure 12: Překlad programů z Clojure do bajtkódu JVM I
    http://www.root.cz/clanky/programovaci-jazyk-clojure-12-preklad-programu-z-clojure-do-bajtkodu-jvm/
  21. Clojure 13: Překlad programů z Clojure do bajtkódu JVM II
    2) http://www.root.cz/clanky/programovaci-jazyk-clojure-13-preklad-programu-z-clojure-do-bajtkodu-jvm-pokracovani/
  22. Clojure 14: Základy práce se systémem maker
    http://www.root.cz/clanky/programovaci-jazyk-clojure-14-zaklady-prace-se-systemem-maker/
  23. Clojure 15: Tvorba uživatelských maker
    http://www.root.cz/clanky/programovaci-jazyk-clojure-15-tvorba-uzivatelskych-maker/
  24. Clojure 16: Složitější uživatelská makra
    http://www.root.cz/clanky/programovaci-jazyk-clojure-16-slozitejsi-uzivatelska-makra/
  25. Clojure 17: Využití standardních maker v praxi
    http://www.root.cz/clanky/programovaci-jazyk-clojure-17-vyuziti-standardnich-maker-v-praxi/
  26. Clojure 18: Základní techniky optimalizace aplikací
    http://www.root.cz/clanky/programovaci-jazyk-clojure-18-zakladni-techniky-optimalizace-aplikaci/
  27. Clojure 19: Vývojová prostředí pro Clojure
    http://www.root.cz/clanky/programovaci-jazyk-clojure-19-vyvojova-prostredi-pro-clojure/
  28. Clojure 20: Vývojová prostředí pro Clojure (Vimu s REPL)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-20-vyvojova-prostredi-pro-clojure-integrace-vimu-s-repl/
  29. Clojure 21: ClojureScript aneb překlad Clojure do JS
    http://www.root.cz/clanky/programovaci-jazyk-clojure-21-clojurescript-aneb-preklad-clojure-do-javascriptu/
Našli jste v článku chybu?
120na80.cz: Pálení žáhy: která jídla ne a co nás uzdraví?

Pálení žáhy: která jídla ne a co nás uzdraví?

Vitalia.cz: Tesco nabízí desítky tun jídla zdarma

Tesco nabízí desítky tun jídla zdarma

Vitalia.cz: Tradiční čínská medicína a rakovina

Tradiční čínská medicína a rakovina

Lupa.cz: Jak levné procesory změnily svět?

Jak levné procesory změnily svět?

Vitalia.cz: Voda z Vltavy před a po úpravě na pitnou

Voda z Vltavy před a po úpravě na pitnou

Podnikatel.cz: Udělali jsme velkou chybu, napsal Čupr

Udělali jsme velkou chybu, napsal Čupr

DigiZone.cz: Samsung EVO-S: novinka pro Skylink

Samsung EVO-S: novinka pro Skylink

Vitalia.cz: Jak Ondra o astma přišel

Jak Ondra o astma přišel

Vitalia.cz: Muž, který miluje příliš. Ženám neimponuje

Muž, který miluje příliš. Ženám neimponuje

DigiZone.cz: Numan Two: rozhlasový přijímač s CD

Numan Two: rozhlasový přijímač s CD

Lupa.cz: Patička e-mailu závazná jako vlastnoruční podpis?

Patička e-mailu závazná jako vlastnoruční podpis?

DigiZone.cz: Funbox 4K v DVB-T2 má ostrý provoz

Funbox 4K v DVB-T2 má ostrý provoz

Vitalia.cz: Fyzioterapeutka: Chůze naboso? Rozhodně ano!

Fyzioterapeutka: Chůze naboso? Rozhodně ano!

Podnikatel.cz: Babišovi se nedá věřit, stěžovali si hospodští

Babišovi se nedá věřit, stěžovali si hospodští

Lupa.cz: Jak se prodává firma za miliardu?

Jak se prodává firma za miliardu?

Podnikatel.cz: Letáky? Lidi zuří, ale ony stále fungují

Letáky? Lidi zuří, ale ony stále fungují

DigiZone.cz: Wimbledon na Nova Sport až do 2019

Wimbledon na Nova Sport až do 2019

DigiZone.cz: Světový pohár v přímém přenosu na ČT

Světový pohár v přímém přenosu na ČT

DigiZone.cz: Digi Slovakia zařazuje stanice SPI

Digi Slovakia zařazuje stanice SPI

DigiZone.cz: Ginx TV: pořad o počítačových hráčích

Ginx TV: pořad o počítačových hráčích