Obsah
1. Použití jazyka Gherkin při tvorbě testovacích scénářů pro aplikace psané v Clojure
2. Příprava projektu, jehož chování se bude testovat předpisem napsaným v Gherkinu
4. Úprava projektového souboru project.clj
5. První pokus o spuštění testovacích scénářů
6. Přidání první verze testovacích scénářů a kostry souboru pro implementaci kroků testu
7. První verze modulu s implementací kroků testu
8. Druhý pokus o spuštění testovacích scénářů
9. Implementace dvou kroků testu
10. Třetí pokus o spuštění testovacích scénářů
11. Implementace zbývajících kroků
12. Čtvrtý pokus o spuštění testovacích scénářů
13. Rozšíření testovacích scénářů
14. Opakování kroků s využitím tabulky v osnově scénáře
15. Pátý pokus o spuštění testovacích scénářů
16. Změna adresáře, v němž jsou uloženy testovací scénáře
17. Výsledná struktura projektu
18. Repositář s demonstračními příklady
19. Odkazy na předchozí části tohoto seriálu
1. Použití jazyka Gherkin při tvorbě testovacích scénářů pro aplikace psané v Clojure
V dnešní části seriálu o programovacím jazyku Clojure částečně navážeme na předchozí článek, v němž jsme se seznámili s knihovnami a moduly pro Leiningen určenými především pro testování aplikací, zejména pro psaní jednotkových testů, popř. pro zjištění pokrytí kódu testy. Víme již, že v této oblasti vzniklo poměrně velké množství zajímavých projektů, například modul humane-test-output, knihovna iota, dále knihovna Expectations a v neposlední řadě i adaptace jazyka Gherkin používaného v BDD neboli Behaviour-driven development.
Dnes si ukážeme, jakým způsobem je možné testy psané v jazyku Gherkin zaintegrovat do projektu psaném v Clojure. Samotné kroky prováděné testy budou napsány s využitím knihovny Expectations, což je – samozřejmě jen pouze podle mého názoru – téměř ideální kombinace (jak již víme, je přímé použití clojure.test příliš nízkoúrovňové).
2. Příprava projektu, jehož chování se bude testovat předpisem napsaným v Gherkinu
V dnešním článku se zaměříme na popis konfigurace projektů (aplikací) psaných v programovacím jazyku Clojure. Ukážeme si, jak je možné zařídit, aby se chování aplikace mohlo testovat předpisem (scénářem, skriptem) napsaným v jazyce Gherkin. V případě Gherkinu se jedná o doménově specifický jazyk (DSL – domain specific language) navržený takovým způsobem, aby bylo možné předpokládané (očekávané) chování aplikace popsat tak jednoduše, že se přípravy popisu bude moci zúčastnit i zákazník-neprogramátor, popř. po krátkém zaučení prakticky jakýkoli člen vývojového týmu. Jazyk Gherkin existuje v různých jazykových mutacích, my se však budeme držet jeho originální anglické varianty.
Obrázek 1: Ukázka scénářů napsaných v jazyce Gherkin.
Jednotlivé kroky testu napsané v jazyce Gherkin je samozřejmě nutné nějakým způsobem implementovat. Pro tento účel použijeme již minule zmíněnou knihovnu Expectations, i když si teoreticky můžeme vystačit pouze s knihovnou clojure.test či dokonce jen s makrem assert (to je ovšem řešení pro masochisty, popř. pro vývojáře placené podle počtu řádků napsaného zdrojového kódu .-).
Na úplném začátku si připravíme kostru projektu, který budeme testovat. Pro vytvoření této kostry použijeme Leiningen. Kostra projektu se vytvoří příkazem:
$ lein new app cucumber+expect1
Výsledkem tohoto příkazu by měla být následující adresářová struktura:
. ├── doc │ └── intro.md ├── LICENSE ├── project.clj ├── README.md ├── resources ├── src │ └── cucumber+expect1 │ └── core.clj └── test └── cucumber+expect1 └── core_test.clj 6 directories, 6 files
3. Testovaná funkce
Dále upravíme soubor src/core.clj takovým způsobem, aby obsahoval testovanou funkci. Pro jednoduchost budeme testovat nám již známý výpočet faktoriálu, který pro záporná čísla na vstupu vyhazuje výjimku IllegalArgumentException (šlo by použít i ArithmeticException). Celý kód může vypadat následovně:
(ns cucumber+expect1.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 1M (inc n))))) ; otestujeme funkci faktorial (defn -main [& args] (doseq [i (range 0 10)] (println i "! = " (factorial i))))
Funkci otestujeme snadno spuštěním -main, a to opět přes Leiningen:
$ lein run 0 ! = 1 1 ! = 1M 2 ! = 2M 3 ! = 6M 4 ! = 24M 5 ! = 120M 6 ! = 720M 7 ! = 5040M 8 ! = 40320M 9 ! = 362880M
4. Úprava projektového souboru project.clj
V dalším kroku přistoupíme k úpravám projektového souboru project.clj. Po vytvoření nového projektu by projektový soubor měl vypadat přibližně takto (pouze si zkontrolujte verzi interpretru jazyka Clojure):
(defproject cucumber+expect1 "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.8.0"]] :main ^:skip-aot cucumber+expect1.core :target-path "target/%s" :profiles {:uberjar {:aot :all}})
Do projektového souboru je nutné přidat hned několik údajů:
- Především budeme používat již výše zmíněnou knihovnuExpectations, kterou přidáme jak do atributu :dependencies, tak i (ve funkci přídavného modulu pro Leiningen) do atributu :plugins. Většinou u nového projektu tento atribut neexistuje, takže ho je nutné do projektového souboru přidat.
- Dále musíme nakonfigurovat přídavný modul pro jazyk Gherkin. Interpret tohoto jazyka je součástí knihovny com.siili/lein-cucumber, kterou přidáme jak do atributu :plugins, tak i do :profiles/:dev (tím zajistíme automatický překlad testů popsaných dále).
- Nakonec musíme nakonfigurovat adresář, v němž budou uloženy skripty psané v Gherkinu a taktéž jednotlivé testovací kroky. Prozatím použijeme adresář „test/features“. Tato konfigurační volba je uložena v atributu :cucumber-feature-paths.
Výsledný projektový soubor project.clj by měl vypadat následovně:
(defproject cucumber+expect1 "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.8.0"] [expectations "2.0.9"]] :plugins [[com.siili/lein-cucumber "1.0.7"] [lein-expectations "0.0.8"]] :cucumber-feature-paths ["test/features/"] :main ^:skip-aot cucumber+expect1.core :target-path "target/%s" :profiles {:uberjar {:aot :all} :dev {:dependencies [[com.siili/lein-cucumber "1.0.7"]]}})
Obrázek 2: Rozdíl mezi původním projektovým souborem a souborem upraveným.
5. První pokus o spuštění testovacích scénářů
Po nakonfigurování projektu se v nabídce příkazu lein objeví nové úlohy, o čemž se můžeme snadno přesvědčit. Následující příkaz je nutné spustit (kdekoli) v adresáři projektu:
$ lein
Ve výsledném seznamu dostupných úloh se objeví i cucumber:
Leiningen is a tool for working with Clojure projects. Several tasks are available: change Rewrite project.clj by applying a function. check Check syntax and warn on reflection. classpath Print the classpath of the current project. clean Remove all files from project's target-path. compile Compile Clojure source into .class files. cucumber Runs Cucumber features in test/features with glue in test/features/step_definitions deploy Build and deploy jar to remote repository. deps Download all dependencies.s do Higher-order task to perform other tasks in succession. expectations Executes expectation tests in your project. help Display a list of tasks or help for a given task. install Install the current project to the local repository. jar Package up all the project's files into a jar file. javac Compile Java source files. new Generate project scaffolding based on a template. plugin DEPRECATED. Please use the :user profile instead. pom Write a pom.xml file to disk for Maven interoperability. release Perform :release-tasks. repl Start a repl session either with the current project or standalone. retest Run only the test namespaces which failed last time around. run Run a -main function with optional command-line arguments. search Search remote maven repositories for matching jars. show-profiles List all available profiles or display one if given an argument. test Run the project's tests. trampoline Run a task without nesting the project's JVM inside Leiningen's. uberjar Package up the project files and dependencies into a jar file. update-in Perform arbitrary transformations on your project map. upgrade Upgrade Leiningen to specified version or latest stable. vcs Interact with the version control system. version Print version for Leiningen and the current JVM. with-profile Apply the given task with the profile(s) specified.
Pokud se nyní pokusíme úlohu cucumber spustit:
$ lein cucumber
vypíše se (dosti dlouhé) chybové hlášení společně s výpisem posloupnosti zásobníkových rámců. Je tomu tak z toho důvodu, že žádné testovací scénáře prozatím nebyly vytvořeny a tudíž je úloha nedokáže najít:
WARNING: update already refers to: #'clojure.core/update in namespace: useful.map, being replaced by: #'useful.map/update WARNING: update already refers to: #'clojure.core/update in namespace: leiningen.core.project, being replaced by: #'useful.map/update Exception in thread "main" java.lang.IllegalArgumentException: Not a file or directory: /home/tester/temp/clojure/clojure-examples/cucumber+expect1/test/features/step_definitions, compiling:(/tmp/form-init4089471517859683101.clj:1:73) at clojure.lang.Compiler.load(Compiler.java:7391) at clojure.lang.Compiler.loadFile(Compiler.java:7317) at clojure.main$load_script.invokeStatic(main.clj:275) at clojure.main$init_opt.invokeStatic(main.clj:277) at clojure.main$init_opt.invoke(main.clj:277) at clojure.main$initialize.invokeStatic(main.clj:308) at clojure.main$null_opt.invokeStatic(main.clj:342) at clojure.main$null_opt.invoke(main.clj:339) at clojure.main$main.invokeStatic(main.clj:421) at clojure.main$main.doInvoke(main.clj:384) at clojure.lang.RestFn.invoke(RestFn.java:421) at clojure.lang.Var.invoke(Var.java:383) at clojure.lang.AFn.applyToHelper(AFn.java:156) at clojure.lang.Var.applyTo(Var.java:700) at clojure.main.main(main.java:37) Caused by: java.lang.IllegalArgumentException: Not a file or directory: /home/tester/temp/clojure/clojure-examples/cucumber+expect1/test/features/step_definitions at cucumber.runtime.io.FileResourceIterator$FileIterator.<init>(FileResourceIterator.java:54) at cucumber.runtime.io.FileResourceIterator.<init>(FileResourceIterator.java:20) at cucumber.runtime.io.FileResourceIterable.iterator(FileResourceIterable.java:19) at clojure.lang.RT.seqFrom(RT.java:532) at clojure.lang.RT.seq(RT.java:523) at clojure.core$seq__4357.invokeStatic(core.clj:137) at clojure.core$seq__4357.invoke(core.clj:137) at cucumber.runtime.clj$_loadGlue.invokeStatic(clj.clj:50) at cucumber.runtime.clj$_loadGlue.invoke(clj.clj:48) at cucumber.runtime.clj.Backend.loadGlue(Unknown Source) at cucumber.runtime.Runtime.<init>(Runtime.java:91) at cucumber.runtime.Runtime.<init>(Runtime.java:69) at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:526) at clojure.lang.Reflector.invokeConstructor(Reflector.java:180) at leiningen.cucumber.util$create_runtime.invokeStatic(util.clj:18) at leiningen.cucumber.util$create_runtime.invoke(util.clj:17) at leiningen.cucumber.util$run_cucumber_BANG_.invokeStatic(util.clj:26) at leiningen.cucumber.util$run_cucumber_BANG_.invoke(util.clj:23) at user$eval1846.invokeStatic(form-init4089471517859683101.clj:1) at user$eval1846.invoke(form-init4089471517859683101.clj:1) at clojure.lang.Compiler.eval(Compiler.java:6927) at clojure.lang.Compiler.eval(Compiler.java:6917) at clojure.lang.Compiler.eval(Compiler.java:6917) at clojure.lang.Compiler.load(Compiler.java:7379) ... 14 more Subprocess failed
6. Přidání první verze testovacích scénářů a kostry souboru pro implementaci kroků testu
Nyní si do projektu (jedná se o jeho novou verzi, proto má ve svém názvu dvojku) přidáme první verzi testovacích scénářů a taktéž (prozatím) kostru modulu psaného v Clojure, do něhož zapíšeme implementace jednotlivých kroků testu. Nově vytvářené soubory budou umístěny do podadresářů test/features a test/features/step_definitions. Výsledek by měl vypadat takto:
. ├── doc │ └── intro.md ├── LICENSE ├── project.clj ├── README.md ├── resources ├── src │ └── cucumber+expect2 │ └── core.clj └── test ├── cucumber+expect2 │ └── core_test.clj └── features ├── factorial.feature └── step_definitions └── factorial_steps.clj 8 directories, 6 files
Soubor factorial.feature, který je umístěný v adresáři test/features, bude obsahovat testovací scénáře napsané v jazyce Gherkin. Ve scénářích jsou použita klíčová slova Feature, Scenario, Given, When a Then, na která postupně navážeme testovací kroky:
Feature: Factorial computation Scenario: Compute factorial for natural numbers Given The function factorial is callable When I try to compute 2! Then I should get result 2 When I try to compute 3! Then I should get result 6 When I try to compute 10! Then I should get result 3628800 Scenario: Compute factorial for negative numbers Given The function factorial is callable When I try to compute -1! Then the IllegalArgumentException should be thrown
Povšimněte si, že Gherkin je z hlediska sémantiky velmi jednoduchý jazyk. Obsahuje deklarace jednotlivých testovacích scénářů, počáteční podmínku a potom sérii kroků ve stylu „když udělám X, stane se (očekávám) Y“. Syntax je také jednoduchá (alespoň prozatím), protože první slovo je klíčové a jednotlivé bloky (zde scénáře) jsou odsazeny, podobně jako v Pythonu či ve formátu YAML.
7. První verze modulu s implementací kroků testu
Soubor factorial_steps.clj je umístěný v adresáři test/features/step_definitions. Právě do tohoto souboru budeme později zapisovat testovací kroky:
(use '[cucumber+expect2.core]) (require '[clojure.test :refer [function?]]) (require '[expectations :refer [expect]])
Vidíme, že prozatím je tento soubor prakticky prázdný; obsahuje pouze nezbytné příkazy pro import potřebných modulů.
8. Druhý pokus o spuštění testovacích scénářů
Zkusme si testovací scénáře spustit, i když prozatím nemáme definované jednotlivé kroky:
$ lein cucumber
Nyní již nedojde k pádu, ale scénář se spustí. Ovšem vzhledem k tomu, že nedojde ke spárování kroků ve scénáři s definovanými kroky testu, vypíše se informace o tom, že je zapotřebí definovat oba scénáře a všech deset kroků:
Running cucumber... Looking for features in: [/home/tester/temp/clojure/clojure-examples/cucumber+expect2/test/features] Looking for glue in: [/home/tester/temp/clojure/clojure-examples/cucumber+expect2/test/features/step_definitions] UUUUUUUUUU 2 Scenarios (2 undefined) 10 Steps (10 undefined) 0m0.000s
Navíc dostaneme i radu, jak by měla implementace jednotlivých kroků vypadat:
You can implement missing steps with the snippets below: (Given #"^The function factorial is callable$" [] (comment Write code here that turns the phrase above into concrete actions ) (throw (cucumber.api.PendingException.))) (When #"^I try to compute (\d+)!$" [arg1] (comment Write code here that turns the phrase above into concrete actions ) (throw (cucumber.api.PendingException.))) (Then #"^I should get result (\d+)$" [arg1] (comment Write code here that turns the phrase above into concrete actions ) (throw (cucumber.api.PendingException.))) (When #"^I try to compute -(\d+)!$" [arg1] (comment Write code here that turns the phrase above into concrete actions ) (throw (cucumber.api.PendingException.))) (Then #"^the IllegalArgumentException should be thrown$" [] (comment Write code here that turns the phrase above into concrete actions ) (throw (cucumber.api.PendingException.))) Ran 0 tests containing 0 assertions in 9 msecs 0 failures, 0 errors.
Obrázek 3: První částečně úspěšné spuštění testovacích scénářů.
9. Implementace dvou kroků testu
Nyní je zapotřebí zjistit, jak vlastně interpret jazyka Gherkin funguje. Ve své nejjednodušší podobě přečte řádek ve scénáři, rozezná první klíčové slovo (Given, When, Then) a posléze se snaží najít takový definovaný krok, na který by bylo možné „spárovat“ zbytek textu na řádku. Jednotlivé kroky testu přitom obsahují regulární výraz, což znamená, že například krok:
When I try to compute 2!
je možné spárovat s krokem definovaným jako:
(When #"^I try to compute (-?\d+)!$" [input] (print "Computing" input "!"))
Povšimněte si, že krok začíná voláním makra When, za nímž skutečně následuje regulární výraz. V tomto výrazu je deklarována jediná skupina (group) – (-?\d+), tj. sekvence číslic, před nimiž může být umístěno znaménko. Dále kód vypadá stejně, jako definice běžné funkce, tj. následuje seznam parametrů, který zde bude odpovídat počtu skupin v regulárním výrazu a samotné tělo funkce. Spárování musí být jednoznačné, protože v opačném případě by Gherkin zahlásil chybu (nejednoznačnost). Pokud se spárování povede, jsou z řetězce (odpovídajícího regulárnímu výrazu) vybrány obsahy jednotlivých skupin a ty jsou předány tělu funkce (resp. makra, ale to zde nehraje žádnou roli.). Zcela stejným způsobem jsou implementovány kroky začínající na Given a Then, popř. na And a But (ale to již předbíháme).
Vzhledem k tomu, že scénáře v Gherkinu vyžadují uložení mezistavu (minimálně mezi When-Then), potřebujeme tento stav nějakým způsobem reprezentovat. Stavu se většinou říká kontext (context) a v Clojure ho můžeme reprezentovat hodnotou typu atom (většinou však bude kontext složitější):
(def context (atom {:input nil :result nil}))
Nyní již můžeme definovat jednotlivé kroky testu. Nejjednodušší je Given, který jen otestuje existenci funkce, ovšem nijak nemění stav:
(Given #"^The function factorial is callable$" [] (assert (clojure.test/function? 'cucumber+expect3.core/factorial)))
Složitější je implementace kroků When a Then, protože zde již pracujeme se stavem. V kroku When přečteme ze scénáře uživatelem zapsanou hodnotu, převedeme ji na číslo pomocí bigdec, uložíme hodnotu do kontextu (swap! context assoc …), vypočteme faktoriál a výsledek taktéž uložíme do kontextu:
(When #"^I try to compute (-?\d+)!$" [input] (let [n (bigdec input)] (swap! context assoc :input n) (swap! context assoc :result (factorial n))))
V kroku Then opět získáme hodnotu zapsanou do scénáře, převedeme ji na číslo a porovnáme s hodnotou vypočtenou. Již zde je vidět použití makra expect z knihovny expectations:
(Then #"^I should get result (\d+)$" [result_str] (let [expected_result (bigdec result_str) actual_result (:result @context)] (expect expected_result actual_result)))
10. Třetí pokus o spuštění testovacích scénářů
Zkusme si testovací scénáře opět spustit:
$ lein cucumber
Nyní již celkem osm kroků proběhlo v pořádku, jeden krok zhavaroval a jeden není definován (to je celkem pokrok):
Running cucumber... Looking for features in: [/home/tester/temp/clojure/clojure-examples/cucumber+expect3/test/features] Looking for glue in: [/home/tester/temp/clojure/clojure-examples/cucumber+expect3/test/features/step_definitions] ........FU Failed scenarios: factorial.feature:13 # Scenario: Compute factorial for negative numbers 2 Scenarios (1 failed, 1 passed) 10 Steps (1 failed, 1 undefined, 8 passed) 0m0.136s java.lang.IllegalArgumentException: negative numbers are not supported! at cucumber_PLUS_expect3.core$factorial.invokeStatic(core.clj:7) at clojure.main.main(main.java:37) at ✽.When I try to compute -1!(factorial.feature:15) You can implement missing steps with the snippets below: (Then #"^the IllegalArgumentException should be thrown$" [] (comment Write code here that turns the phrase above into concrete actions ) (throw (cucumber.api.PendingException.))) Ran 1 tests containing 1 assertions in 11 msecs 0 failures, 0 errors.
Obrázek 4: Druhé částečně úspěšné spuštění testovacích scénářů.
11. Implementace zbývajících kroků
Implementace zbývajících kroků bude relativně snadná. Nejdříve rozšíříme kontext o informaci, zda došlo k výjimce a o jakou výjimku se jedná:
(def context (atom {:input nil :result nil :exception nil}))
Další krok je shodný s předchozí verzí testu:
(Given #"^The function factorial is callable$" [] (assert (clojure.test/function? 'cucumber+expect4.core/factorial)))
Změní se ovšem krok s výpočtem faktoriálu. Pokud nedojde k výjimce, uloží se do kontextu výsledek, pokud k výjimce dojde, uloží se do kontextu objekt s výjimkou (tento kód by bylo možné napsat kratším způsobem, ale méně čitelně):
(When #"^I try to compute (-?\d+)!$" [input] (let [n (bigdec input)] (swap! context assoc :input n) (try (swap! context assoc :result (factorial n)) (swap! context dissoc :exception) (catch Exception e (swap! context dissoc :result) (swap! context assoc :exception e)))))
Krok s testem výsledku obsahuje podmínku, zda nenastala výjimka:
(Then #"^I should get result (\d+)$" [result_str] (let [expected_result (bigdec result_str) actual_result (:result @context) exception (:exception @context)] (expect nil? exception) (expect expected_result actual_result)))
Dále následuje definice kroků, které očekávají výjimku. Povšimněte si, že oba kroky mohou začínat stejným řetězcem a přitom je rozlišení, který krok se má zavolat, stále zcela jednoznačné:
L(defn exception-name [exception] (-> exception .getClass .getSimpleName)) (Then #"^the ([A-Za-z]+) should be thrown$" [exception_name] (let [exception (:exception @context) result (:result @context)] (expect nil? result) (expect exception) (expect exception_name (exception-name exception)))) (Then #"^the ([A-Za-z]+) should be thrown with message (.+)$" [exception_name message] (let [exception (:exception @context) result (:result @context)] (expect nil? result) (expect exception) (expect exception_name (exception-name exception))))
12. Čtvrtý pokus o spuštění testovacích scénářů
Zkusme si testovací scénáře opět spustit tak, jak jsme zvyklí:
$ lein cucumber
Tentokrát se (konečně!) vypíše informace o úspěšném dokončení testovacího scénáře:
Running cucumber... Looking for features in: [/home/tester/temp/clojure/clojure-examples/cucumber+expect4/test/features] Looking for glue in: [/home/tester/temp/clojure/clojure-examples/cucumber+expect4/test/features/step_definitions] .......... 2 Scenarios (2 passed) 10 Steps (10 passed) 0m0.130s Ran 5 tests containing 5 assertions in 13 msecs 0 failures, 0 errors.
Obrázek 5: Úspěšné dokončení testovacích scénářů.
13. Rozšíření testovacích scénářů
Testovací scénáře je nyní možné relativně snadno upravovat, a to – což je důležité – bez jakýchkoli zásahů do definicí testů. To znamená, že scénář může převzít kolega, který například Clojure nedokáže přečíst, ovšem má dobrou znalost aplikační domény. Jednou z velmi zajímavých možností, jak scénáře rozšířit, spočívá v tom, že se specifikuje tabulka či tabulky se vstupními hodnotami a očekávanými výsledky.
Do testovacího scénáře přidáme tzv. osnovu (Scenario Outline):
Scenario Outline: Compute more factorials for natural numbers Given The function factorial is callable When I try to compute <n>! Then I should get result <result> Examples: | n | result | | 1 | 1 | | 2 | 2 | | 3 | 6 | | 4 | 24 | | 10 | 3628800 |
Tento scénář se bude pro každý řádek tabulky opakovat, přičemž v každé iteraci se namísto textů <n> a <result> dosadí hodnoty z příslušného sloupce tabulky. Jedná se přitom o pouhou textovou substituci, takže ve skutečnosti je možné s tabulkami provádět i dosti složité operace (viz též navazující článek).
Podobně můžeme vytvořit osnovu pro ty vstupní hodnoty, u nichž očekáváme vznik výjimky:
Scenario Outline: Compute more factorials for negative numbers Given The function factorial is callable When I try to compute <n>! Then the IllegalArgumentException should be thrown Examples: | n | |-1 | |-2 | |-3 | |-1000 |
14. Opakování kroků s využitím tabulky v osnově scénáře
Celý scénář bude vypadat následovně:
Feature: Factorial computation Scenario: Compute factorial for natural numbers Given The function factorial is callable When I try to compute 2! Then I should get result 2 When I try to compute 3! Then I should get result 6 When I try to compute 10! Then I should get result 3628800 Scenario Outline: Compute more factorials for natural numbers Given The function factorial is callable When I try to compute <n>! Then I should get result <result> Examples: | n | result | | 1 | 1 | | 2 | 2 | | 3 | 6 | | 4 | 24 | | 10 | 3628800 | Scenario: Compute factorial for negative numbers Given The function factorial is callable When I try to compute -1! Then the IllegalArgumentException should be thrown Scenario Outline: Compute more factorials for negative numbers Given The function factorial is callable When I try to compute <n>! Then the IllegalArgumentException should be thrown Examples: | n | |-1 | |-2 | |-3 | |-1000 |
15. Pátý pokus o spuštění testovacích scénářů
Popáté si zkusme testy spustit:
$ lein cucumber
Povšimněte si neobvykle vysokého počtu kroků. Je to samozřejmě způsobeno tím, že každé opakování osnovy vede k provedení dvou kroků testu:
Running cucumber... Looking for features in: [/home/tester/temp/clojure/clojure-examples/cucumber+expect5/test/features] Looking for glue in: [/home/tester/temp/clojure/clojure-examples/cucumber+expect5/test/features/step_definitions] ..................................... 11 Scenarios (11 passed) 37 Steps (37 passed) 0m0.126s Ran 5 tests containing 5 assertions in 13 msecs 0 failures, 0 errors.
Obrázek 6: Další úspěšné dokončení všech testovacích scénářů.
16. Změna adresáře, v němž jsou uloženy testovací scénáře
Pokud budete chtít změnit adresář, v němž jsou uloženy testovací scénáře, je to velmi snadné. Stačí malý zásah do projektového souboru (viz zvýrazněnou část):
(defproject cucumber+expect6 "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.8.0"] [expectations "2.0.9"]] :plugins [[com.siili/lein-cucumber "1.0.7"] [lein-expectations "0.0.8"]] :cucumber-feature-paths ["features/"] :main ^:skip-aot cucumber+expect6.core :target-path "target/%s" :profiles {:uberjar {:aot :all} :dev {:dependencies [[com.siili/lein-cucumber "1.0.7"]]}})
17. Výsledná struktura projektu
Takto nakonfigurovaný projekt by měl mít následující strukturu:
. ├── doc │ └── intro.md ├── features │ ├── factorial.feature │ └── step_definitions │ └── factorial_steps.clj ├── LICENSE ├── project.clj ├── README.md ├── resources ├── src │ └── cucumber+expect6 │ └── core.clj └── test └── cucumber+expect6 └── core_test.clj 8 directories, 8 files
Příště si popíšeme některé další zajímavé vlastnosti jazyka Gherkin a jeho spojení s Clojure.
18. Repositář s demonstračními příklady
Všech šest demonstračních příkladů a projektů určených pro Clojure verze 1.9.0 popř. Clojure 1.8.0 bylo uloženo do repositáře, který naleznete na adrese https://github.com/tisnik/clojure-examples:
Projekt | Popis | Odkaz |
---|---|---|
1 | cucumber-demo | https://github.com/tisnik/clojure-examples/tree/master/cucumber-demo |
2 | cucumber+expect1 | https://github.com/tisnik/clojure-examples/tree/master/cucumber+expect1 |
3 | cucumber+expect2 | https://github.com/tisnik/clojure-examples/tree/master/cucumber+expect2 |
4 | cucumber+expect3 | https://github.com/tisnik/clojure-examples/tree/master/cucumber+expect3 |
5 | cucumber+expect4 | https://github.com/tisnik/clojure-examples/tree/master/cucumber+expect4 |
6 | cucumber+expect5 | https://github.com/tisnik/clojure-examples/tree/master/cucumber+expect5 |
7 | cucumber+expect6 | https://github.com/tisnik/clojure-examples/tree/master/cucumber+expect6 |
19. Odkazy na předchozí části tohoto seriálu
- 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/ - 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/ - 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: 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/ - 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/ - Programovací jazyk Clojure – některé užitečné triky použitelné (nejenom) v testech
http://www.root.cz/clanky/programovaci-jazyk-clojure-nektere-uzitecne-triky-pouzitelne-nejenom-v-testech/ - Enlive – výkonný šablonovací systém pro jazyk Clojure
http://www.root.cz/clanky/enlive-vykonny-sablonovaci-system-pro-jazyk-clojure/ - Nástroj Leiningen a programovací jazyk Clojure: tvorba vlastních knihoven pro veřejný repositář Clojars
http://www.root.cz/clanky/nastroj-leiningen-a-programovaci-jazyk-clojure-tvorba-vlastnich-knihoven-pro-verejny-repositar-clojars/ - Novinky v Clojure verze 1.8.0
http://www.root.cz/clanky/novinky-v-clojure-verze-1–8–0/ - Asynchronní programování v Clojure s využitím knihovny core.async
http://www.root.cz/clanky/asynchronni-programovani-v-clojure-s-vyuzitim-knihovny-core-async/ - Asynchronní programování v Clojure s využitím knihovny core.async (pokračování)
http://www.root.cz/clanky/asynchronni-programovani-v-clojure-s-vyuzitim-knihovny-core-async-pokracovani/ - Asynchronní programování v Clojure s využitím knihovny core.async (dokončení)
http://www.root.cz/clanky/asynchronni-programovani-v-clojure-s-vyuzitim-knihovny-core-async-dokonceni/ - Vytváříme IRC bota v programovacím jazyce Clojure
http://www.root.cz/clanky/vytvarime-irc-bota-v-programovacim-jazyce-clojure/ - Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/ - Multimetody v Clojure aneb polymorfismus bez použití OOP
https://www.root.cz/clanky/multimetody-v-clojure-aneb-polymorfismus-bez-pouziti-oop/ - Práce s externími Java archivy v programovacím jazyku Clojure
https://www.root.cz/clanky/prace-s-externimi-java-archivy-v-programovacim-jazyku-clojure/ - Pixie: lehký skriptovací jazyk s „kouzelnými“ schopnostmi
https://www.root.cz/clanky/pixie-lehky-skriptovaci-jazyk-s-kouzelnymi-schopnostmi/ - Programovací jazyk Pixie: funkce ze základní knihovny a použití FFI
https://www.root.cz/clanky/programovaci-jazyk-pixie-funkce-ze-zakladni-knihovny-a-pouziti-ffi/ - Novinky v Clojure verze 1.9.0
https://www.root.cz/clanky/novinky-v-clojure-verze-1–9–0/ - Validace dat s využitím knihovny spec v Clojure 1.9.0
https://www.root.cz/clanky/validace-dat-s-vyuzitim-knihovny-spec-v-clojure-1–9–0/ - Knihovny a moduly usnadňující testování aplikací naprogramovaných v jazyce Clojure
https://www.root.cz/clanky/knihovny-a-moduly-usnadnujici-testovani-aplikaci-naprogramovanych-v-jazyce-clojure/
20. Odkazy na Internetu
- BusinessReadableDSL
https://martinfowler.com/bliki/BusinessReadableDSL.html - Gherkin: Feature – introduction
https://github.com/cucumber/cucumber/wiki/Feature-Introduction - BDD 101: The Gherkin Language
https://automationpanda.com/2017/01/26/bdd-101-the-gherkin-language/ - Gherkin: Given/When/Then
https://github.com/cucumber/cucumber/wiki/Given-When-Then - Humane test output for clojure.test
https://github.com/pjstadig/humane-test-output - iota
https://github.com/juxt/iota - 5 Differences between clojure.spec and Schema
https://lispcast.com/clojure.spec-vs-schema/ - Schema: Clojure(Script) library for declarative data description and validation
https://github.com/plumatic/schema - Zip archiv s Clojure 1.9.0
http://repo1.maven.org/maven2/org/clojure/clojure/1.9.0/clojure-1.9.0.zip - Clojure 1.9 is now available
https://clojure.org/news/2017/12/08/clojure19 - Deps and CLI Guide
https://clojure.org/guides/deps_and_cli - Changes to Clojure in Version 1.9
https://github.com/clojure/clojure/blob/master/changes.md - clojure.spec – Rationale and Overview
https://clojure.org/about/spec - Zip archiv s Clojure 1.8.0
http://repo1.maven.org/maven2/org/clojure/clojure/1.8.0/clojure-1.8.0.zip - Clojure 1.8 is now available
http://clojure.org/news/2016/01/19/clojure18 - Socket Server REPL
http://dev.clojure.org/display/design/Socket+Server+REPL - CLJ-1671: Clojure socket server
http://dev.clojure.org/jira/browse/CLJ-1671 - CLJ-1449: Add clojure.string functions for portability to ClojureScript
http://dev.clojure.org/jira/browse/CLJ-1449 - Launching a Socket Server
http://clojure.org/reference/repl_and_main#_launching_a_socket_server - API for clojure.string
http://clojure.github.io/clojure/branch-master/clojure.string-api.html - Clojars:
https://clojars.org/ - Seznam knihoven na Clojars:
https://clojars.org/projects - Clojure Cookbook: Templating HTML with Enlive
https://github.com/clojure-cookbook/clojure-cookbook/blob/master/07_webapps/7–11_enlive.asciidoc - An Introduction to Enlive
https://github.com/swannodette/enlive-tutorial/ - Enlive na GitHubu
https://github.com/cgrand/enlive - 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.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