Obsah
1. Makro are určené pro zpřehlednění testů
2. Referenčně transparentní funkce a funkce s vedlejšími efekty
3. Jak testovat funkce, které tisknou na standardní či chybový výstup?
5. Mockování funkcí volaných v průběhu testování
7. Zjištění existence funkcí či proměnných v testovaném jmenném prostoru
8. Predikát clojure.test/function?
9. Repositář s dnešními demonstračními příklady
10. Odkazy na předchozí části tohoto seriálu
1. Makro are určené pro zpřehlednění testů
V dnešním článku o programovacím jazyku Clojure se seznámíme s některými jednoduchými i složitějšími triky, které lze využít při tvorbě testů. První trik spočívá ve využití makra are, které může v mnoha případech zpřehlednit testy, v nichž se neustále volá nám již známé makro is. Jedná se o velmi jednoduchý trik, nicméně si jeho použití ukažme na demonstračním příkladu. Nejprve vytvoříme kostru tohoto příkladu:
lein new app testing1
Dále upravíme module testing1.core následujícím způsobem:
(ns testing1.core (:gen-class)) (defn add [x y] (println "Adding" x "to" y) (+ x y)) (defn -main [& args] (println (add 1 2)))
Pokusme se nyní napsat jednoduchý test pro funkci add. Test bude uložen v souboru testing1/test/testing1/core_test.clj, jehož kostra byla automaticky vygenerována nástrojem Leiningen, takže nám zbývá jen dopsání vlastní testovací funkce:
(ns testing1.core-test (:require [clojure.test :refer :all] [testing1.core :refer :all])) (deftest test-add-1 (testing "function add" (is (= 0 (add 0 0))) (is (= 3 (add 1 2))) (is (= 5/6 (add 1/2 1/3)))))
Takto zapsaný test je plně funkční, o čemž se lze snadno přesvědčit:
lein test lein test testing1.core-test Adding 0 to 0 Adding 1 to 2 Adding 1/2 to 1/3 Ran 1 tests containing 3 assertions. 0 failures, 0 errors.
Jedinou vážnější nevýhodou je opakované použití makra is a z toho vyplývající záplavy závorek. Aby se psaní testů zpřehlednilo, lze využít makro are, kterému se předá funkce provádějící porovnání (jen se nezapisuje jméno funkce) a za tímto zápisem pak již většinou seznam obsahující očekávané hodnoty a volání testované funkce:
(ns testing1.core-test (:require [clojure.test :refer :all] [testing1.core :refer :all])) (deftest test-add-1 (testing "function add" (is (= 0 (add 0 0))) (is (= 3 (add 1 2))) (is (= 5/6 (add 1/2 1/3))))) (deftest test-add-2 (testing "function add" (are [x y] (= x y) 0 (add 0 0) 3 (add 1 2) 5/6 (add 1/2 1/3))))
Oba dva testy test-add-1 a test-add-2 jsou prakticky ekvivalentní, o čemž se opět můžeme snadno přesvědčit:
lein test Adding 0 to 0 Adding 1 to 2 Adding 1/2 to 1/3 Adding 0 to 0 Adding 1 to 2 Adding 1/2 to 1/3 Ran 2 tests containing 6 assertions. 0 failures, 0 errors.
2. Referenčně transparentní funkce a funkce s vedlejšími efekty
Již v úvodních článcích o programovacím jazyce Clojure jsme si řekli, že tento jazyk patří, společně s klasickým LISPem, Scheme, Haskellem či Erlangem do skupiny (ne vždy nutně čistě) funkcionálních jazyků, tj. programovacích jazyků vycházejících z teorie takzvaného λ-kalkulu, jehož autorem je Alonzo Church (na první návrhy LISPu se dokonce můžeme dívat jako na jeden z formalizovaných způsobů zápisu λ-kalkulu, pro nějž jen tak mimochodem existuje mechanismus vyhodnocování jednotlivých λ výrazů; taktéž se tím vysvětluje přítomnost znaku lambda v logu jazyka Clojure). Ve skutečnosti sice Clojure není čistě funkcionálním jazykem, ovšem v případě, že vývojář bude při tvorbě svých aplikací dodržovat zásady funkcionálního programování, bude pro něj mnohem snadnější vytvářet skutečně výkonné aplikace; ať se to již týká snadnější tvorby bezpečných vícevláknových aplikací či možnosti použití mnohdy velmi užitečné funkce memoize.
Připomeňme si, že v programovacím jazyce Clojure jsou funkce považovány za plnohodnotné datové typy, což znamená, že funkce lze navázat na libovolný symbol (a tím vlastně původně anonymní funkci pojmenovat), funkce lze předávat jako parametry do jiných funkcí a funkce mohou být taktéž návratovou hodnotou jiných funkcí – funkce tedy může vytvořit a vrátit jinou funkci. Clojure taktéž podporuje práci s uzávěry (closure(s)), tj. funkcí svázaných s nějakým symbolem vytvořeným vně funkce. Podpora uzávěrů umožňuje například tvorbu funkcí sdílejících společný kontext (GUI) atd. Ovšem vzhledem k tomu, že – jak již víme – Clojure není čistě funkcionálním jazykem, je možné při vytváření uživatelských funkcí přímo z dané funkce přistupovat k nějakému globálnímu symbolu, přesněji řečeno k symbolu „globálnímu“ v rámci nějakého jmenného prostoru. Taktéž lze vytvářet funkce s vedlejším efektem, které například zapisují data do souborů, mění hodnotu navázanou na globální symboly atd.
Vývojáři by však neměli tyto možnosti nabízené programovacím jazykem Clojure zneužívat, protože tím znemožňují využití některých optimalizačních technik a v neposlední řadě si taktéž komplikují možnost testování takto vytvořených funkcí. Namísto toho se ukazuje být velmi výhodné vytvářet již v perexu zmíněné takzvané referenčně transparentní funkce, což jsou funkce, které nepřistupují k žádným globálním symbolům, nemají žádný vedlejší efekt ani si nepamatují žádný vnitřní stav (příkladem „funkce“ s vnitřním stavem je například Math/random). Referenčně transparentní funkci jsou při jejím volání předány parametry a funkce pouze na základě hodnot předaných parametrů vrátí nějaký výsledek. Tato (pochopitelná) vlastnost má jeden důležitý důsledek – chování referenčně transparentní funkce je nezávislé na stavu aplikace a je taktéž zcela nezávislé na tom, kdy je funkce zavolána.
Pravděpodobně nejjednodušší ukázka funkce, která není referenčně transparentní:
(defn random [] (java.lang.Math/random))
Referenčně transparentní funkce jsou typicky používány ve frameworku Clojure Ring:
(defn handler [request] (let [params (:params request) x (param->number params "x") y (param->number params "y") result (compute-result x y)] (-> (response/response (render-html-page x y result)) (response/content-type "text/html; charset=utf-8"))))
3. Jak testovat funkce, které tisknou na standardní či chybový výstup?
Při tvorbě reálných aplikací v programovacím jazyku Clojure se poměrně často setkáme s následujícím problémem: máme za úkol otevřít nějaký zdroj dat (databázi, soubor…), přečíst z tohoto zdroje data a posléze zdroj dat opět zavřít. Dalším praktickým problémem je nastavení přesnosti a zaokrouhlovacího režimu pro objekty typu BigDecimal, provedení nějaké matematické operace a následně obnovení původní přesnosti a zaokrouhlovacího režimu. Všechny tyto problémy tedy vyžadují, aby se provedla nějaká nastavovací operace, která většinou mění prostředí programu, posléze se provede vlastní výpočet/sekvence příkazů a nakonec se obnoví původní prostředí programu. Takto chápané operace se v programovacím jazyce Clojure poměrně často implementují s využitím maker, jejichž název začíná na with-. Příkladem může být makro with-precision pro dočasnou změnu přesnosti a zaokrouhlovacího režimu pro objekty typu BigDecimal:
user=> (with-precision 1 (/ 1M 3)) 0.3M user=> (with-precision 2 (/ 1M 3)) 0.33M user=> (with-precision 10 (/ 1M 3)) 0.3333333333M user=>
Proč se vlastně o makrech with-* zmiňujeme zrovna v dnešním článku věnovaném tvorbě testů? Určité problémy s testováním totiž nastanou již ve chvíli, kdy testovaná funkce provádí tisk na standardní či na chybový výstup. Již tato „maličkost“ totiž ve skutečnosti porušuje referenční transparentnost (funkce mění stav objektu *out* či *err*), což se vlastně ihned projeví ve chvíli, kdy je v testu nutné ověřit, zda funkce skutečně na standardní/chybový výstup tiskne korektní zprávy. Jedna z možností, jak takové funkce testovat, spočívá v použití makra nazvaného with-out-str, které dokáže „zachytit“ všechna volání funkcí print a println. Řetězce, které by se pomocí těchto funkcí normálně vypsaly na standardní výstup, jsou namísto využity jako návratová hodnota makra:
user=> (with-out-str (print "Hello ") (print "world") (print '!)) "Hello world!" user=> (def vystup (with-out-str (print "Hello ") (print "world") (print '!))) #'user/vystup user=> vystup "Hello world!" user=>
Jak již bylo řečeno v úvodním odstavci, pracují makra with- takovým způsobem, že dočasně změní prostředí programu. Nejinak je tomu i v případě makra with-out-str, kde se dočasně změní objekt navázaný na symbol *out*, jenž představuje standardní výstup, neboli v řeči Javy System.out. Symbol *out* je při provádění příkazů předaných makru přesměrován na novou instanci objektu java.io.StringWriter, který je po provedení těla převeden na řetězec s využitím funkce str:
(defmacro with-out-str "Evaluates exprs in a context in which *out* is bound to a fresh StringWriter. Returns the string created by any nested printing calls." {:added "1.0"} [& body] `(let [s# (new java.io.StringWriter)] (binding [*out* s#] ~@body (str s#))))
4. Makro with-out-str
Doplňme si nyní test pro první demonstrační příklad o zjištění, zda funkce add tiskne na standardní výstup očekávané zprávy. Rozšíření testu není složité, protože pouze namísto návratové hodnoty testujeme text zachycený makrem with-out-str:
(ns testing1.core-test (:require [clojure.test :refer :all] [testing1.core :refer :all])) (deftest test-add-1 (testing "function add" (is (= 0 (add 0 0))) (is (= 3 (add 1 2))) (is (= 5/6 (add 1/2 1/3))))) (deftest test-add-2 (testing "function add" (are [x y] (= x y) 0 (add 0 0) 3 (add 1 2) 5/6 (add 1/2 1/3)))) (deftest test-add-3 (testing "function add" (is (= "Adding 0 to 0\n" (with-out-str (add 0 0)))) (is (= "Adding 1 to 2\n" (with-out-str (add 1 2)))) (is (= "Adding 1/2 to 1/3\n" (with-out-str (add 1/2 1/3)))))) (deftest test-add-4 (testing "function add" (are [x y] (= x y) "Adding 0 to 0\n" (with-out-str (add 0 0)) "Adding 1 to 2\n" (with-out-str (add 1 2)) "Adding 1/2 to 1/3\n" (with-out-str (add 1/2 1/3)))))
Rozšířený test odzkoušíme:
lein test lein test testing1.core-test Adding 0 to 0 Adding 1 to 2 Adding 1/2 to 1/3 Adding 0 to 0 Adding 1 to 2 Adding 1/2 to 1/3 Ran 4 tests containing 12 assertions. 0 failures, 0 errors.
Poznámka: řádky začínající na „Adding“ byly vypsány v prvních dvou testech test-add-1 a test-add-2, zatímco ve zbývajících testech test-add-3 a test-add-4 byl výstup zachycen a nijak viditelně se při spuštění testů neprojeví.
5. Mockování funkcí volaných v průběhu testování
Podívejme se nyní na nepatrně složitější demonstrační příklad nazvaný testing2, v němž jsou použity funkce pracující s SQL databází SQLite. Projektový soubor tohoto demonstračního příkladu vypadá následovně (povšimněte si především dvou nových prvků uložených ve vektoru :dependencies:
(defproject testing2 "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"] [org.clojure/java.jdbc "0.3.5"] [org.xerial/sqlite-jdbc "3.7.2"]] :main ^:skip-aot testing2.core :target-path "target/%s" :profiles {:uberjar {:aot :all}})
V hlavním modulu jsou mj. deklarovány funkce pro čtení z databáze i pro zápis do databáze:
(ns testing2.core (:gen-class)) (require '[clojure.java.jdbc :as jdbc]) (def changes-db {:classname "org.sqlite.JDBC" :subprotocol "sqlite" :subname "changes.db" }) (defn read-changes-for-user [user-name] (jdbc/query changes-db [(str "select * from changes where user_name=? order by id;") user-name])) (defn store-changes [user-name package description date] (jdbc/insert! changes-db :changes {:date_time date :user_name user-name :package package :description description})) (defn count-changes-for-user [user-name] (count (read-changes-for-user user-name))) (defn -main [& args] (println "Hello, World!"))
Problém může nastat ve chvíli, kdy je zapotřebí otestovat například funkci read-changes-for-user nebo store-changes. Existuje několik způsobů, jak je možné tento problém vyřešit; například se namísto skutečné databáze použije jen její omezená varianta (typicky in-memory databáze naplněná jen nejnutnějšími a předem známými daty). V programovacím jazyce Clojure však existuje ještě jedna možnost – mocking (to je ale slovo!) funkcí jdbc/query a jdbc/insert a jejich náhrada za uživatelsky definované funkce, které se vůči okolnímu kódu chovají jako původní funkce, ale ve skutečnosti provádí jinou činnost, například namísto čtení z databáze vrací konstantu atd. V následující kapitole si ukážeme, jak se tato problematika řeší s využitím makra with-redefs.
6. Makro with-redefs
Testovací modul, který bude (například) testovat korektnost funkcí read-changes-for-user a count-changes-for-user může používat makro nazvané with-redefs. V těle tohoto makra je původní symbol navázán na jinou hodnotu. V našem konkrétním příkladu potřebujeme na symbol jdbc/query (povšimněte si použití jmenného prostoru!) navázat jinou funkci. Původní funkce clojure.java.jdbc/query je volána se dvěma parametry, což bude platit i pro novou uživatelskou funkci:
(with-redefs [jdbc/query (fn [db-spec query] (tělo_nové_anonymní_funkce))] ; ; v těle makra je jdbc/query navázáno na novou anonymní funkci ; )
Důležité je, že mimo tělo makra with-redefs je na symbol jdbc/query navázána původní funkce pracující s SQL databázemi!
Testovací modul lze napsat například následujícím způsobem. Povšimněte si, že v testu test-read-changes-for-user vrací lokálně deklarovaná anonymní funkce navázaná na symbol jdbc/query druhý prvek druhého parametru (což je předané jméno), zatímco v testu test-count-changes-for-user se vrací konstantní desetiprvkový vektor:
(ns testing2.core-test (:require [clojure.test :refer :all] [testing2.core :refer :all])) (require '[clojure.java.jdbc :as jdbc]) (deftest test-read-changes-for-user (testing "read-changes-for-user" ; use mock instead of jdbc/query (with-redefs [jdbc/query (fn [db-spec query] (second query))] (is (= "Pavel" (read-changes-for-user "Pavel")))))) (deftest test-count-changes-for-user (testing "read-changes-for-user" ; use mock instead of jdbc/query (with-redefs [jdbc/query (fn [db-spec query] [1 2 3 4 5 6 7 8 9 10])] (is (= 10 (count-changes-for-user "Pavel"))))))
Test si opět odzkoušíme:
lein test lein test testing2.core-test Ran 2 tests containing 2 assertions. 0 failures, 0 errors.
Poznámka: lokálně nahradit je možné prakticky libovolnou funkci z libovolného jmenného prostoru.
Poznámka 2: namísto makra with-redefs je možné použít i přímo funkci with-redefs-fn, která je mimochodem v makru with-redefs volána.
7. Zjištění existence funkcí či proměnných v testovaném jmenném prostoru
V některých případech je nutné v testu zjistit, zda nějaká funkce či proměnná existuje. Pro tento účel se mi vyplatilo deklarovat si pomocnou funkci (přesněji řečeno predikát) nazvanou callable?. Tomuto predikátu se předá libovolný symbol a pokud je tento symbol navázán na skutečnou funkci, vrátí se hodnota true, jinak false:
(defn callable? "Test if given function-name is bound to the real function." [function-name] (clojure.test/function? function-name))
Test na existenci proměnné (či proměnných) lze zajistit predikátem bound?, který se použije následujícím způsobem:
(bound? (find-var 'jméno.modulu/symbol)
8. Predikát clojure.test/function?
Jen stručně se podívejme na použití výše zmíněného predikátu function? při psaní testů. Mějme modul, který se má otestovat:
(ns testing3.core (:gen-class)) (defn add [x y] (println "Adding" x "to" y) (+ x y)) (defn -main [& args] (println (add 1 2)))
Dva nové testy nazvané test-main-existence a test-add-existence pouze zjišťují existenci funkce -main a add z hlavního modulu, tedy konkrétně ze jmenného prostoru testing3.core:
(ns testing3.core-test (:require [clojure.test :refer :all] [testing3.core :refer :all])) (defn callable? "Test if given function-name is bound to the real function." [function-name] (clojure.test/function? function-name)) (deftest test-main-existence "Check that the testing3.core/-main definition exists." (testing "if the testing3.core/-main definition exists." (is (callable? 'testing3.core/-main)))) (deftest test-add-existence "Check that the testing3.core/add definition exists." (testing "if the testing3.core/add definition exists." (is (callable? 'testing3.core/add)))) (deftest test-add-1 (testing "function add" (is (= 0 (add 0 0))) (is (= 3 (add 1 2))) (is (= 5/6 (add 1/2 1/3)))))
Obligátní zjištění, zda všechny testy proběhnou bez chyb:
lein test Adding 0 to 0 Adding 1 to 2 Adding 1/2 to 1/3 Ran 3 tests containing 5 assertions. 0 failures, 0 errors.
9. Repositář s dnešními demonstračními příklady
Všechny tři dnes zmíněné demonstrační příklady byly, podobně jako v předchozích částech tohoto seriálu, uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/clojure-examples. V tabulce zobrazené pod tímto odstavcem naleznete na zdrojové kódy jednotlivých demonstračních příkladů přímé odkazy:
# | Příklad | Github |
---|---|---|
1 | testing1 | https://github.com/tisnik/clojure-examples/tree/master/testing1 |
2 | testing2 | https://github.com/tisnik/clojure-examples/tree/master/testing2 |
3 | testing3 | https://github.com/tisnik/clojure-examples/tree/master/testing3 |
10. Odkazy na předchozí části tohoto seriálu
- 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/ - Leiningen: nástroj pro správu projektů napsaných v Clojure (3)
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-3/ - Leiningen: nástroj pro správu projektů napsaných v Clojure (4)
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-4/ - Leiningen: nástroj pro správu projektů napsaných v Clojure (5)
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-5/ - Leiningen: nástroj pro správu projektů napsaných v Clojure (6)
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-6/ - Programovací jazyk Clojure a databáze (1.část)
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-databaze-1-cast/ - Pluginy pro Leiningen
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-pluginy-pro-leiningen/ - Programovací jazyk Clojure a knihovny pro práci s vektory a maticemi
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-knihovny-pro-praci-s-vektory-a-maticemi/ - Programovací jazyk Clojure a knihovny pro práci s vektory a maticemi (2)
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-knihovny-pro-praci-s-vektory-a-maticemi-2/ - Programovací jazyk Clojure: syntéza procedurálních textur s využitím knihovny Clisk
http://www.root.cz/clanky/programovaci-jazyk-clojure-synteza-proceduralnich-textur-s-vyuzitim-knihovny-clisk/ - Programovací jazyk Clojure: syntéza procedurálních textur s využitím knihovny Clisk (2)
http://www.root.cz/clanky/programovaci-jazyk-clojure-synteza-proceduralnich-textur-s-vyuzitim-knihovny-clisk-2/ - Programovací jazyk Clojure: syntéza procedurálních textur s využitím knihovny Clisk (dokončení)
http://www.root.cz/clanky/programovaci-jazyk-clojure-synteza-proceduralnich-textur-s-vyuzitim-knihovny-clisk-dokonceni/ - Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure
http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure/ - Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure (2)
http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure-2/ - Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure (3)
http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure-3/ - Programovací jazyk Clojure a práce s Gitem
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem/ - Programovací jazyk Clojure a práce s Gitem (2)
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem-2/ - Programovací jazyk Clojure – triky při práci s řetězci
http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-retezci/ - Programovací jazyk Clojure – triky při práci s kolekcemi
http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-kolekcemi/ - Programovací jazyk Clojure – práce s mapami a množinami
http://www.root.cz/clanky/programovaci-jazyk-clojure-prace-s-mapami-a-mnozinami/ - Programovací jazyk Clojure – základy zpracování XML
http://www.root.cz/clanky/programovaci-jazyk-clojure-zaklady-zpracovani-xml/ - Programovací jazyk Clojure – testování s využitím knihovny Expectations
http://www.root.cz/clanky/programovaci-jazyk-clojure-testovani-s-vyuzitim-knihovny-expectations/
11. Odkazy na Internetu
- Expectations: příklady atd.
http://jayfields.com/expectations/ - Expectations na GitHubu
https://github.com/jaycfields/expectations - Lein-expectations na GitHubu
https://github.com/gar3thjon3s/lein-expectations - Testing Clojure With Expectations
https://semaphoreci.com/blog/2014/09/23/testing-clojure-with-expectations.html - Clojure testing TDD/BDD libraries: clojure.test vs Midje vs Expectations vs Speclj
https://www.reddit.com/r/Clojure/comments/1viilt/clojure_testing_tddbdd_libraries_clojuretest_vs/ - Testing: One assertion per test
http://blog.jayfields.com/2007/06/testing-one-assertion-per-test.html - Rewriting Your Test Suite in Clojure in 24 hours
http://blog.circleci.com/rewriting-your-test-suite-in-clojure-in-24-hours/ - Clojure doc: zipper
http://clojuredocs.org/clojure.zip/zipper - Clojure doc: parse
http://clojuredocs.org/clojure.xml/parse - Clojure doc: xml-zip
http://clojuredocs.org/clojure.zip/xml-zip - Clojure doc: xml-seq
http://clojuredocs.org/clojure.core/xml-seq - Parsing XML in Clojure
https://github.com/clojuredocs/guides - Clojure Zipper Over Nested Vector
https://vitalyper.wordpress.com/2010/11/23/clojure-zipper-over-nested-vector/ - Understanding Clojure's PersistentVector implementation
http://blog.higher-order.net/2009/02/01/understanding-clojures-persistentvector-implementation - Understanding Clojure's PersistentHashMap (deftwice…)
http://blog.higher-order.net/2009/09/08/understanding-clojures-persistenthashmap-deftwice.html - Assoc and Clojure's PersistentHashMap: part ii
http://blog.higher-order.net/2010/08/16/assoc-and-clojures-persistenthashmap-part-ii.html - Ideal Hashtrees (paper)
http://lampwww.epfl.ch/papers/idealhashtrees.pdf - Clojure home page
http://clojure.org/ - Clojure (downloads)
http://clojure.org/downloads - Clojure Sequences
http://clojure.org/sequences - Clojure Data Structures
http://clojure.org/data_structures - The Structure and Interpretation of Computer Programs: 2.2.1 Representing Sequences
http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-15.html#%_sec2.2.1 - The Structure and Interpretation of Computer Programs: 3.3.1 Mutable List Structure
http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-22.html#%_sec3.3.1 - Clojure – Functional Programming for the JVM
http://java.ociweb.com/mark/clojure/article.html - Clojure quick reference
http://faustus.webatu.com/clj-quick-ref.html - 4Clojure
http://www.4clojure.com/ - ClojureDoc (rozcestník s dokumentací jazyka Clojure)
http://clojuredocs.org/ - Clojure (na Wikipedia EN)
http://en.wikipedia.org/wiki/Clojure - Clojure (na Wikipedia CS)
http://cs.wikipedia.org/wiki/Clojure - SICP (The Structure and Interpretation of Computer Programs)
http://mitpress.mit.edu/sicp/ - Pure function
http://en.wikipedia.org/wiki/Pure_function - Funkcionální programování
http://cs.wikipedia.org/wiki/Funkcionální_programování - Čistě funkcionální (datové struktury, jazyky, programování)
http://cs.wikipedia.org/wiki/Čistě_funkcionální - Clojure Macro Tutorial (Part I, Getting the Compiler to Write Your Code For You)
http://www.learningclojure.com/2010/09/clojure-macro-tutorial-part-i-getting.html - Clojure Macro Tutorial (Part II: The Compiler Strikes Back)
http://www.learningclojure.com/2010/09/clojure-macro-tutorial-part-ii-compiler.html - Clojure Macro Tutorial (Part III: Syntax Quote)
http://www.learningclojure.com/2010/09/clojure-macro-tutorial-part-ii-syntax.html - Tech behind Tech: Clojure Macros Simplified
http://techbehindtech.com/2010/09/28/clojure-macros-simplified/ - Fatvat – Exploring functional programming: Clojure Macros
http://www.fatvat.co.uk/2009/02/clojure-macros.html - Eulerovo číslo
http://cs.wikipedia.org/wiki/Eulerovo_číslo - List comprehension
http://en.wikipedia.org/wiki/List_comprehension - List Comprehensions in Clojure
http://asymmetrical-view.com/2008/11/18/list-comprehensions-in-clojure.html - Clojure Programming Concepts: List Comprehension
http://en.wikibooks.org/wiki/Clojure_Programming/Concepts#List_Comprehension - Clojure core API: for macro
http://clojure.github.com/clojure/clojure.core-api.html#clojure.core/for - cirrus machina – The Clojure for macro
http://www.cirrusmachina.com/blog/comment/the-clojure-for-macro/ - Riastradh's Lisp Style Rules
http://mumble.net/~campbell/scheme/style.txt - Dynamic Languages Strike Back
http://steve-yegge.blogspot.cz/2008/05/dynamic-languages-strike-back.html - Scripting: Higher Level Programming for the 21st Century
http://www.tcl.tk/doc/scripting.html - Java Virtual Machine Support for Non-Java Languages
http://docs.oracle.com/javase/7/docs/technotes/guides/vm/multiple-language-support.html - Třída java.lang.String
http://docs.oracle.com/javase/7/docs/api/java/lang/String.html - Třída java.lang.StringBuffer
http://docs.oracle.com/javase/7/docs/api/java/lang/StringBuffer.html - Třída java.lang.StringBuilder
http://docs.oracle.com/javase/7/docs/api/java/lang/StringBuilder.html - StringBuffer versus String
http://www.javaworld.com/article/2076072/build-ci-sdlc/stringbuffer-versus-string.html - Threading macro (dokumentace k jazyku Clojure)
https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/-> - Understanding the Clojure → macro
http://blog.fogus.me/2009/09/04/understanding-the-clojure-macro/ - clojure.inspector
http://clojure.github.io/clojure/clojure.inspector-api.html - The Clojure Toolbox
http://www.clojure-toolbox.com/ - 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:
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/ - Clojure.org: Vars and the Global Environment
http://clojure.org/Vars - Clojure.org: Refs and Transactions
http://clojure.org/Refs - Clojure.org: Atoms
http://clojure.org/Atoms - Clojure.org: Agents as Asynchronous Actions
http://clojure.org/agents - Transient Data Structureshttp://clojure.org/transients