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
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, module1 a module2, 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.clj a test/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ý:
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 Ring a Hiccup.
12. Odkazy na Internetu
- Leiningen: nástroj pro správu projektů napsaných v Clojure
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure/ - 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/ - Unit Testing in Clojure
http://nakkaya.com/2009/11/18/unit-testing-in-clojure/ - Testing in Clojure (Part-1: Unit testing)
http://blog.knoldus.com/2014/03/22/testing-in-clojure-part-1-unit-testing/ - API for clojure.test – Clojure v1.6 (stable)
https://clojure.github.io/clojure/clojure.test-api.html - Leiningen: úvodní stránka
http://leiningen.org/ - Leiningen: Git repository
https://github.com/technomancy/leiningen - leiningen-win-installer
http://leiningen-win-installer.djpowell.net/ - Clojure 1: Úvod
http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm/ - Clojure 2: Symboly, kolekce atd.
http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-2-cast/ - 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/ - 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/ - 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/ - Clojure 6: Podpora pro paralelní programování
http://www.root.cz/clanky/programovaci-jazyk-clojure-6-futures-nejsou-jen-financni-derivaty/ - Clojure 7: Další funkce pro paralelní programování
http://www.root.cz/clanky/programovaci-jazyk-clojure-7-dalsi-podpurne-prostredky-pro-paralelni-programovani/ - 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/ - 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/ - Clojure 10: Kooperace mezi Clojure a Javou
http://www.root.cz/clanky/programovaci-jazyk-clojure-10-kooperace-mezi-clojure-a-javou-pokracovani/ - Clojure 11: Generátorová notace seznamu/list comprehension
http://www.root.cz/clanky/programovaci-jazyk-clojure-11-generatorova-notace-seznamu-list-comprehension/ - 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/ - 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/ - Clojure 14: Základy práce se systémem maker
http://www.root.cz/clanky/programovaci-jazyk-clojure-14-zaklady-prace-se-systemem-maker/ - Clojure 15: Tvorba uživatelských maker
http://www.root.cz/clanky/programovaci-jazyk-clojure-15-tvorba-uzivatelskych-maker/ - Clojure 16: Složitější uživatelská makra
http://www.root.cz/clanky/programovaci-jazyk-clojure-16-slozitejsi-uzivatelska-makra/ - Clojure 17: Využití standardních maker v praxi
http://www.root.cz/clanky/programovaci-jazyk-clojure-17-vyuziti-standardnich-maker-v-praxi/ - Clojure 18: Základní techniky optimalizace aplikací
http://www.root.cz/clanky/programovaci-jazyk-clojure-18-zakladni-techniky-optimalizace-aplikaci/ - Clojure 19: Vývojová prostředí pro Clojure
http://www.root.cz/clanky/programovaci-jazyk-clojure-19-vyvojova-prostredi-pro-clojure/ - 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/ - Clojure 21: ClojureScript aneb překlad Clojure do JS
http://www.root.cz/clanky/programovaci-jazyk-clojure-21-clojurescript-aneb-preklad-clojure-do-javascriptu/