Hlavní navigace

Použití jazyka Gherkin při tvorbě testovacích scénářů pro aplikace psané v Clojure

Pavel Tišnovský

Dnes si ukážeme způsob použití doménově specifického jazyka Gherkin při tvorbě testovacích scénářů pro aplikace napsané v programovacím jazyku Clojure. Při implementaci testů bude použita knihovna Expectations.

Doba čtení: 29 minut

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

3. Testovaná funkce

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

20. Odkazy na Internetu

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é).

Poznámka: v dalším textu se, podobně jako v předchozím článku, předpokládá, že máte nainstalován správce projektu Leiningen. Pokud tomu tak není, bude nutné si Leiningen nainstalovat, což ve skutečnosti není nic těžkého. Navíc se jedná o velmi užitečný projekt s mnoha přídavnými moduly, které využijete nejenom při testování, ale například i při přípravě dokumentace nebo ve chvíli, kdy se aplikace připravuje na deployment. To, zda je Leiningen nainstalován a lze ho spustit, zjistíte například příkazem which lein.

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
Poznámka: povšimněte si, že v názvu projektu lze použít i znak +. Ten se objeví i v názvu jmenného prostoru. Tento znak nemá v Clojure žádný speciální význam.

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ů:

  1. 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.
  2. 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).
  3. 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:

19. Odkazy na předchozí části tohoto seriálu

  1. Clojure 1: Úvod
    http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm/
  2. Clojure 2: Symboly, kolekce atd.
    http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-2-cast/
  3. 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/
  4. 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/
  5. 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/
  6. Clojure 6: Podpora pro paralelní programování
    http://www.root.cz/clanky/programovaci-jazyk-clojure-6-futures-nejsou-jen-financni-derivaty/
  7. Clojure 7: Další funkce pro paralelní programování
    http://www.root.cz/clanky/programovaci-jazyk-clojure-7-dalsi-podpurne-prostredky-pro-paralelni-programovani/
  8. 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/
  9. 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/
  10. Clojure 10: Kooperace mezi Clojure a Javou
    http://www.root.cz/clanky/programovaci-jazyk-clojure-10-kooperace-mezi-clojure-a-javou-pokracovani/
  11. Clojure 11: Generátorová notace seznamu/list comprehension
    http://www.root.cz/clanky/programovaci-jazyk-clojure-11-generatorova-notace-seznamu-list-comprehension/
  12. 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/
  13. 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/
  14. Clojure 14: Základy práce se systémem maker
    http://www.root.cz/clanky/programovaci-jazyk-clojure-14-zaklady-prace-se-systemem-maker/
  15. Clojure 15: Tvorba uživatelských maker
    http://www.root.cz/clanky/programovaci-jazyk-clojure-15-tvorba-uzivatelskych-maker/
  16. Clojure 16: Složitější uživatelská makra
    http://www.root.cz/clanky/programovaci-jazyk-clojure-16-slozitejsi-uzivatelska-makra/
  17. Clojure 17: Využití standardních maker v praxi
    http://www.root.cz/clanky/programovaci-jazyk-clojure-17-vyuziti-standardnich-maker-v-praxi/
  18. Clojure 18: Základní techniky optimalizace aplikací
    http://www.root.cz/clanky/programovaci-jazyk-clojure-18-zakladni-techniky-optimalizace-aplikaci/
  19. Clojure 19: Vývojová prostředí pro Clojure
    http://www.root.cz/clanky/programovaci-jazyk-clojure-19-vyvojova-prostredi-pro-clojure/
  20. 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/
  21. Clojure 21: ClojureScript aneb překlad Clojure do JS
    http://www.root.cz/clanky/programovaci-jazyk-clojure-21-clojurescript-aneb-preklad-clojure-do-javascriptu/
  22. Leiningen: nástroj pro správu projektů napsaných v Clojure
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure/
  23. 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/
  24. 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/
  25. 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/
  26. 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/
  27. 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/
  28. Programovací jazyk Clojure a databáze (1.část)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-databaze-1-cast/
  29. Pluginy pro Leiningen
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-pluginy-pro-leiningen/
  30. 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/
  31. 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/
  32. 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/
  33. 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/
  34. Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure
    http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure/
  35. 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/
  36. 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/
  37. Programovací jazyk Clojure a práce s Gitem
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem/
  38. 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/
  39. Programovací jazyk Clojure a práce s Gitem (2)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem-2/
  40. Programovací jazyk Clojure – triky při práci s řetězci
    http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-retezci/
  41. Programovací jazyk Clojure – triky při práci s kolekcemi
    http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-kolekcemi/
  42. Programovací jazyk Clojure – práce s mapami a množinami
    http://www.root.cz/clanky/programovaci-jazyk-clojure-prace-s-mapami-a-mnozinami/
  43. Programovací jazyk Clojure – základy zpracování XML
    http://www.root.cz/clanky/programovaci-jazyk-clojure-zaklady-zpracovani-xml/
  44. Programovací jazyk Clojure – testování s využitím knihovny Expectations
    http://www.root.cz/clanky/programovaci-jazyk-clojure-testovani-s-vyuzitim-knihovny-expectations/
  45. 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/
  46. Enlive – výkonný šablonovací systém pro jazyk Clojure
    http://www.root.cz/clanky/enlive-vykonny-sablonovaci-system-pro-jazyk-clojure/
  47. 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/
  48. Novinky v Clojure verze 1.8.0
    http://www.root.cz/clanky/novinky-v-clojure-verze-1–8–0/
  49. 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/
  50. 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/
  51. 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/
  52. Vytváříme IRC bota v programovacím jazyce Clojure
    http://www.root.cz/clanky/vytvarime-irc-bota-v-programovacim-jazyce-clojure/
  53. Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
    https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/
  54. Multimetody v Clojure aneb polymorfismus bez použití OOP
    https://www.root.cz/clanky/multimetody-v-clojure-aneb-polymorfismus-bez-pouziti-oop/
  55. 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/
  56. Pixie: lehký skriptovací jazyk s „kouzelnými“ schopnostmi
    https://www.root.cz/clanky/pixie-lehky-skriptovaci-jazyk-s-kouzelnymi-schopnostmi/
  57. Programovací jazyk Pixie: funkce ze základní knihovny a použití FFI
    https://www.root.cz/clanky/pro­gramovaci-jazyk-pixie-funkce-ze-zakladni-knihovny-a-pouziti-ffi/
  58. Novinky v Clojure verze 1.9.0
    https://www.root.cz/clanky/novinky-v-clojure-verze-1–9–0/
  59. 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/
  60. 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

  1. BusinessReadableDSL
    https://martinfowler.com/bli­ki/BusinessReadableDSL.html
  2. Gherkin: Feature – introduction
    https://github.com/cucumber/cu­cumber/wiki/Feature-Introduction
  3. BDD 101: The Gherkin Language
    https://automationpanda.com/2017/01/26/bdd-101-the-gherkin-language/
  4. Gherkin: Given/When/Then
    https://github.com/cucumber/cu­cumber/wiki/Given-When-Then
  5. Humane test output for clojure.test
    https://github.com/pjstadig/humane-test-output
  6. iota
    https://github.com/juxt/iota
  7. 5 Differences between clojure.spec and Schema
    https://lispcast.com/clojure.spec-vs-schema/
  8. Schema: Clojure(Script) library for declarative data description and validation
    https://github.com/plumatic/schema
  9. Zip archiv s Clojure 1.9.0
    http://repo1.maven.org/ma­ven2/org/clojure/clojure/1­.9.0/clojure-1.9.0.zip
  10. Clojure 1.9 is now available
    https://clojure.org/news/2017/12/08/clo­jure19
  11. Deps and CLI Guide
    https://clojure.org/guides/dep­s_and_cli
  12. Changes to Clojure in Version 1.9
    https://github.com/clojure/clo­jure/blob/master/changes.md
  13. clojure.spec – Rationale and Overview
    https://clojure.org/about/spec
  14. Zip archiv s Clojure 1.8.0
    http://repo1.maven.org/ma­ven2/org/clojure/clojure/1­.8.0/clojure-1.8.0.zip
  15. Clojure 1.8 is now available
    http://clojure.org/news/2016/01/19/clo­jure18
  16. Socket Server REPL
    http://dev.clojure.org/dis­play/design/Socket+Server+REPL
  17. CLJ-1671: Clojure socket server
    http://dev.clojure.org/jira/browse/CLJ-1671
  18. CLJ-1449: Add clojure.string functions for portability to ClojureScript
    http://dev.clojure.org/jira/browse/CLJ-1449
  19. Launching a Socket Server
    http://clojure.org/referen­ce/repl_and_main#_launchin­g_a_socket_server
  20. API for clojure.string
    http://clojure.github.io/clo­jure/branch-master/clojure.string-api.html
  21. Clojars:
    https://clojars.org/
  22. Seznam knihoven na Clojars:
    https://clojars.org/projects
  23. Clojure Cookbook: Templating HTML with Enlive
    https://github.com/clojure-cookbook/clojure-cookbook/blob/master/07_webapps/7–11_enlive.asciidoc
  24. An Introduction to Enlive
    https://github.com/swannodette/enlive-tutorial/
  25. Enlive na GitHubu
    https://github.com/cgrand/enlive
  26. Expectations: příklady atd.
    http://jayfields.com/expectations/
  27. Expectations na GitHubu
    https://github.com/jaycfi­elds/expectations
  28. Lein-expectations na GitHubu
    https://github.com/gar3thjon3s/lein-expectations
  29. Testing Clojure With Expectations
    https://semaphoreci.com/blog/2014/09/23/tes­ting-clojure-with-expectations.html
  30. Clojure testing TDD/BDD libraries: clojure.test vs Midje vs Expectations vs Speclj
    https://www.reddit.com/r/Clo­jure/comments/1viilt/cloju­re_testing_tddbdd_librari­es_clojuretest_vs/
  31. Testing: One assertion per test
    http://blog.jayfields.com/2007/06/tes­ting-one-assertion-per-test.html
  32. Rewriting Your Test Suite in Clojure in 24 hours
    http://blog.circleci.com/rewriting-your-test-suite-in-clojure-in-24-hours/
  33. Clojure doc: zipper
    http://clojuredocs.org/clo­jure.zip/zipper
  34. Clojure doc: parse
    http://clojuredocs.org/clo­jure.xml/parse
  35. Clojure doc: xml-zip
    http://clojuredocs.org/clojure.zip/xml-zip
  36. Clojure doc: xml-seq
    http://clojuredocs.org/clo­jure.core/xml-seq
  37. Parsing XML in Clojure
    https://github.com/clojuredocs/guides
  38. Clojure Zipper Over Nested Vector
    https://vitalyper.wordpres­s.com/2010/11/23/clojure-zipper-over-nested-vector/
  39. Understanding Clojure's PersistentVector implementation
    http://blog.higher-order.net/2009/02/01/understanding-clojures-persistentvector-implementation
  40. Understanding Clojure's PersistentHashMap (deftwice…)
    http://blog.higher-order.net/2009/09/08/understanding-clojures-persistenthashmap-deftwice.html
  41. Assoc and Clojure's PersistentHashMap: part ii
    http://blog.higher-order.net/2010/08/16/assoc-and-clojures-persistenthashmap-part-ii.html
  42. Ideal Hashtrees (paper)
    http://lampwww.epfl.ch/pa­pers/idealhashtrees.pdf
  43. Clojure home page
    http://clojure.org/
  44. Clojure (downloads)
    http://clojure.org/downloads
  45. Clojure Sequences
    http://clojure.org/sequences
  46. Clojure Data Structures
    http://clojure.org/data_structures
  47. 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
  48. 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
  49. Clojure – Functional Programming for the JVM
    http://java.ociweb.com/mar­k/clojure/article.html
  50. Clojure quick reference
    http://faustus.webatu.com/clj-quick-ref.html
  51. 4Clojure
    http://www.4clojure.com/
  52. ClojureDoc (rozcestník s dokumentací jazyka Clojure)
    http://clojuredocs.org/
  53. Clojure (na Wikipedia EN)
    http://en.wikipedia.org/wiki/Clojure
  54. Clojure (na Wikipedia CS)
    http://cs.wikipedia.org/wiki/Clojure
  55. SICP (The Structure and Interpretation of Computer Programs)
    http://mitpress.mit.edu/sicp/
  56. Pure function
    http://en.wikipedia.org/wi­ki/Pure_function
  57. Funkcionální programování
    http://cs.wikipedia.org/wi­ki/Funkcionální_programová­ní
  58. Čistě funkcionální (datové struktury, jazyky, programování)
    http://cs.wikipedia.org/wi­ki/Čistě_funkcionální
  59. 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
  60. Clojure Macro Tutorial (Part II: The Compiler Strikes Back)
    http://www.learningclojure­.com/2010/09/clojure-macro-tutorial-part-ii-compiler.html
  61. Clojure Macro Tutorial (Part III: Syntax Quote)
    http://www.learningclojure­.com/2010/09/clojure-macro-tutorial-part-ii-syntax.html
  62. Tech behind Tech: Clojure Macros Simplified
    http://techbehindtech.com/2010/09/28/clo­jure-macros-simplified/
  63. Fatvat – Exploring functional programming: Clojure Macros
    http://www.fatvat.co.uk/2009/02/clo­jure-macros.html
  64. Eulerovo číslo
    http://cs.wikipedia.org/wi­ki/Eulerovo_číslo
  65. List comprehension
    http://en.wikipedia.org/wi­ki/List_comprehension
  66. List Comprehensions in Clojure
    http://asymmetrical-view.com/2008/11/18/list-comprehensions-in-clojure.html
  67. Clojure Programming Concepts: List Comprehension
    http://en.wikibooks.org/wi­ki/Clojure_Programming/Con­cepts#List_Comprehension
  68. Clojure core API: for macro
    http://clojure.github.com/clo­jure/clojure.core-api.html#clojure.core/for
  69. cirrus machina – The Clojure for macro
    http://www.cirrusmachina.com/blog/com­ment/the-clojure-for-macro/
  70. Riastradh's Lisp Style Rules
    http://mumble.net/~campbe­ll/scheme/style.txt
  71. Dynamic Languages Strike Back
    http://steve-yegge.blogspot.cz/2008/05/dynamic-languages-strike-back.html
  72. Scripting: Higher Level Programming for the 21st Century
    http://www.tcl.tk/doc/scripting.html
  73. Java Virtual Machine Support for Non-Java Languages
    http://docs.oracle.com/ja­vase/7/docs/technotes/gui­des/vm/multiple-language-support.html
  74. Třída java.lang.String
    http://docs.oracle.com/ja­vase/7/docs/api/java/lang/Strin­g.html
  75. Třída java.lang.StringBuffer
    http://docs.oracle.com/ja­vase/7/docs/api/java/lang/Strin­gBuffer.html
  76. Třída java.lang.StringBuilder
    http://docs.oracle.com/ja­vase/7/docs/api/java/lang/Strin­gBuilder.html
  77. StringBuffer versus String
    http://www.javaworld.com/ar­ticle/2076072/build-ci-sdlc/stringbuffer-versus-string.html
  78. Threading macro (dokumentace k jazyku Clojure)
    https://clojure.github.io/clo­jure/clojure.core-api.html#clojure.core/->
  79. Understanding the Clojure → macro
    http://blog.fogus.me/2009/09/04/un­derstanding-the-clojure-macro/
  80. clojure.inspector
    http://clojure.github.io/clo­jure/clojure.inspector-api.html
  81. The Clojure Toolbox
    http://www.clojure-toolbox.com/
  82. Unit Testing in Clojure
    http://nakkaya.com/2009/11/18/unit-testing-in-clojure/
  83. Testing in Clojure (Part-1: Unit testing)
    http://blog.knoldus.com/2014/03/22/tes­ting-in-clojure-part-1-unit-testing/
  84. API for clojure.test – Clojure v1.6 (stable)
    https://clojure.github.io/clo­jure/clojure.test-api.html
  85. Leiningen: úvodní stránka
    http://leiningen.org/
  86. Leiningen: Git repository
    https://github.com/techno­mancy/leiningen
  87. leiningen-win-installer
    http://leiningen-win-installer.djpowell.net/
  88. Clojure.org: Vars and the Global Environment
    http://clojure.org/Vars
  89. Clojure.org: Refs and Transactions
    http://clojure.org/Refs
  90. Clojure.org: Atoms
    http://clojure.org/Atoms
  91. Clojure.org: Agents as Asynchronous Actions
    http://clojure.org/agents
  92. Transient Data Structureshttp://clojure.or­g/transients
Našli jste v článku chybu?