Hlavní navigace

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

Pavel Tišnovský

V dnešním článku dokončíme téma, kterému jsme se začali věnovat minule. Jedná se o použití doménově specifického jazyka Gherkin pro tvorbu testovacích scénářů v programovacím jazyku Clojure.

Doba čtení: 29 minut

11. Spuštění testovacích scénářů kompletního projektu

12. Vylepšení testovacího scénáře – vyhledání informací o větším množství uživatelů

13. Opětovné spuštění testovacích scénářů

14. Malá úprava testů pro uživatele GitHubu bez vyplněné společnosti

15. Testovací scénář použitelný pro libovolné REST API, nejenom pro GitHub

16. Úprava kroků testu

17. Repositář s demonstračními příklady

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

19. Odkazy na Internetu

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

V dnešním článku o programovacím jazyce Clojure dokončíme téma, kterému jsme se začali věnovat minule. Již v předchozím článku jsme si totiž ukázali, jakým způsobem je možné zaintegrovat testovací scénáře popsané v jazyku Gherkin do projektu naprogramovaného v Clojure. Samotné kroky prováděné v testech byly 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é). Dnes si ukážeme poněkud praktičtější použití při testování REST API. Pro jednoduchost nebudeme testovat API vlastního serveru, ale budeme testy spouštět oproti API GitHubu, které je dostatečně zdokumentováno a pro některé operace se dokonce ani nebudeme muset přihlašovat :-)

Obrázek 1: Ukázka scénářů napsaných v jazyce Gherkin.

2. Jednoduchý testovací scénář pro kontrolu funkčnosti REST API GitHubu

Nejprve si připravíme soubor s prozatím jediným testovacím scénářem. V tomto scénáři, který bude skutečně velmi jednoduchý, budeme vycházet z předpokladu (Given), že je dostupný web GitHubu. Pokud tomu tak je, tak se pokusíme přistoupit na výchozí bod REST API GitHubu metodou GET (When). Očekáváme přitom, že služba odpoví běžným způsobem a vrátí stavový kód 200 OK (Then). Celý scénář je uložen v souboru nazvaném github.feature, vypadá následovně:

Feature: GitHub API tests
 
  @smoketest
  Scenario: Check the GitHub API entry point
    Given GitHub is accessible
    When I access the API endpoint /
    Then I should receive response with 200 status code

Poznámka: řádkem obsahujícím @smoketest byl ke scénáři přiřazen tag. Díky použití tagů lze jednoduše vybírat, které testy se mají spustit.

3. Struktura projektu pro spuštění testovacího scénáře

Testovací scénář bude uložen do nového projektu, který nazveme (kvůli mé malé obrazotvornosti) jednoduše „cucumber+expect7“. Základní strukturu projektu opět, jak je již zvykem, vytvoříme příkazem:

$ lein new app cucumber+expect7

Výsledkem by měla být tato adresářová struktura:

├── doc
│   └── intro.md
├── LICENSE
├── project.clj
├── README.md
├── resources
├── src
│   └── cucumber+expect7
│       └── core.clj
└── test
    └── cucumber+expect7
        └── core_test.clj
 
6 directories, 6 files

Následně do projektu přidáme adresář „features“ a v něm vytvoříme soubor s výše popsaným testovacím scénářem. Dále ještě vytvoříme podadresář „features/step_definitions“ s prozatím prázdným souborem pojmenovaným „github_steps.clj“. Nová adresářová struktura projektu bude nyní vypadat následovně:

├── bdd
├── doc
│   └── intro.md
├── features
│   ├── github.feature
│   └── step_definitions
│       └── github_steps.clj
├── LICENSE
├── project.clj
├── README.md
├── resources
├── src
│   └── cucumber+expect7
│       └── core.clj
└── test
    └── cucumber+expect7
        └── core_test.clj
 
8 directories, 9 files

Provést musíme ještě jednu důležitou změnu, a to konkrétně úpravu projektového souboru „project.clj“. Do něj přidáme řádky popisující přídavné moduly a knihovny, které použijeme v testech. Kromě toho také musíme přidat klíč s informací o tom, kde se mají hledat testovací scénáře:

(defproject cucumber+expect7 "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+expect7.core
    :target-path "target/%s"
    :profiles {:uberjar {:aot :all}
               :dev {:dependencies [[com.siili/lein-cucumber "1.0.7"]]}})

4. Implementace jednotlivých kroků testovacího scénáře

Nyní musíme implementovat jednotlivé kroky testovacího scénáře. Tyto kroky budou uloženy ve zdrojovém kódu step_definitions/github_steps.clj. Celý skript je rozdělen do několika částí.

Nejdříve jsou uvedeny importy dalších modulů, které budou v testu použity. Zejména budeme využívat již popsanou knihovnu Expectations a v ní deklarované makro expect:

(use '[cucumber+expect7.core])
(require '[expectations :refer [expect]])

Následně si pro jednoduchost vytvoříme dva symboly, v nichž budou uloženy adresy GitHubu i základ adresy služeb GitHubu dostupných přes REST API (v ideálním případě by se tyto konstanty načetly ze souborů, více viz další text):

(def URL "https://github.com")
(def API-URL "https://api.github.com")

Další symbol je navázán na atom obsahující kontext testů, tj. stav, který může být v jednotlivých krocích buď měněn nebo testován jeho obsah. O významu kontextu jsme se zmínili minule (ve skutečnosti není nutné používat zrovna atom, ale je to nejjednodušší řešení):

(def context (atom {:response nil}))

Další část skriptu obsahuje definici funkce určené pro přístup k REST API či k libovolné URL. První řádek není nutné použít v případě, že Clojure používá Javu 8 nebo Javu 9, ovšem v Javě 7 či dokonce v Javě 6 je nutné povolit novější verze TLS. Ostatně i z tohoto důvodu zde nepoužívám žádnou knihovnu typu clj-http, protože se tím nastavení protokolu https dosti komplikuje (popravdě řečeno jsem nepřišel na to, jak tento problém v Javě 7 a Javě 6 elegantně řešit). Samotná funkce request je jednoduchá – otevře připojení na zadanou adresu, přečte data ze vstupního proudu a uloží tato data a současně i stavový kód HTTP do kontextu:

(System/setProperty "https.protocols" "TLSv1,TLSv1.1,TLSv1.2")
 
(defn request
    [address]
    (let [url        (new java.net.URL address)
          connection (.openConnection url)]
          (.connect connection)
          {:status  (.getResponseCode connection)
           :content (slurp (.getInputStream connection))}))

Následuje implementace všech tří kroků testu – předpokladu, který musí být splněn před spuštěním, kroku, v němž přistupujeme k URL GitHubu a konečně ke kontrole stavového kódu HTTP, který se vrátil. Očekávaný kód získáme parsingem řádku zapsaného v testovacím scénáři:

(Given #"^GitHub is accessible$"
    []
    (let [response (request URL)]
        (expect (:status response) 200)))
 
 
(When #"^I access the API endpoint /$"
    []
    (let [response (request (str API-URL "/"))]
        (swap! context assoc :response response)))
 
 
(Then #"^I should receive response with (\d+) status code$"
    [code]
    (let [expected_code (Integer/parseInt code)
          actual_code   (-> @context :response :status)]
          (expect expected_code actual_code)))
Mohlo by se zdát, že namísto vlastní funkce request by se mohla přímo volat funkce slurp, která dokáže načíst data ze zadané URL. To je sice opravdu možné, ovšem my potřebujeme znát i stavový kód HTTP (tedy 200, 500, 401, 404 atd. atd.).

5. Spuštění testovacího scénáře

Připomeňme si, že o spuštění testovacího scénáře se postará příkaz:

$ lein cucumber

Pokud tento příkaz skutečně spustíme, měly by se vypsat následující řádky (samozřejmě za předpokladu, že je GitHub dostupný). Výsledek bude vypadat zhruba následovně:

Running cucumber...
Looking for features in:  [/home/tester/temp/clojure/clojure-examples/cucumber+expect7/features]
Looking for glue in:  [/home/tester/temp/clojure/clojure-examples/cucumber+expect7/features/step_definitions]
...
 
1 Scenarios (1 passed)
3 Steps (3 passed)
0m1.627s
 
 
Ran 2 tests containing 2 assertions in 11 msecs
0 failures, 0 errors.

Z výsledků je patrné, že se scénář (prozatím jediný) skutečně spustil a všechny tři jeho kroky proběhly bez chyby.

Obrázek 2: Skutečná podoba výstupu po spuštění testovacího scénáře z příkazové řádky.

6. Zjednodušení projektu s testovacím scénářem

Ve skutečnosti byl předchozí projekt příliš složitý a obsahoval několik adresářů a souborů, které jsme žádným způsobem nemohli využít. Je tomu tak z toho důvodu, že základní struktura projektu byla vytvořena nástrojem Leiningen, který pouze využívá předpřipravené šablony. Pro naše účely však vůbec nebudeme potřebovat ani zdrojové kódy uložené v adresáři „src“ ani jednotkové testy uložené standardně v adresáři „test“. Původní struktura projektu vypadala následovně:

├── bdd
├── doc
│   └── intro.md
├── features
│   ├── github.feature
│   └── step_definitions
│       └── github_steps.clj
├── LICENSE
├── project.clj
├── README.md
├── resources
├── src
│   └── cucumber+expect7
│       └── core.clj
└── test
    └── cucumber+expect7
        └── core_test.clj
 
8 directories, 9 files

Pokud odstraníme výše zmíněné adresáře „src“ a „test“ i s jejich obsahem, získáme jednodušší projekt:

├── bdd
├── doc
│   └── intro.md
├── features
│   ├── github.feature
│   └── step_definitions
│       └── github_steps.clj
├── LICENSE
├── project.clj
├── README.md
└── resources
 
4 directories, 7 files

Navíc ovšem musíme z projektového souboru „project.clj“ odstranit řádek:

    :main ^:skip-aot cucumber+expect7.core

Výsledný soubor, který je tentokrát součástí druhého demonstračního příkladu, vypadá takto:

(defproject cucumber+expect8 "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/"]
    :target-path "target/%s"
    :profiles {:uberjar {:aot :all}
               :dev {:dependencies [[com.siili/lein-cucumber "1.0.7"]]}})

Od této chvíle samozřejmě nebude možné použít příkaz pro spuštění aplikace (protože žádná skutečná aplikace neexistuje):

$ lein run
 
No :main namespace specified in project.clj.

Pokus o spuštění jednotkových testů také dopadne poněkud podezřele (i když je pravda, že pokud žádné testy neexistují, je statistika vypsána dobře):

$ lein test
 
lein test user
 
Ran 0 tests containing 0 assertions.
0 failures, 0 errors.

Spuštění testovacího scénáře je naproti tomu samozřejmě možné:

$ lein cucumber
 
Running cucumber...
Looking for features in:  [/home/tester/temp/clojure/clojure-examples/cucumber+expect8/features]
Looking for glue in:  [/home/tester/temp/clojure/clojure-examples/cucumber+expect8/features/step_definitions]
...
 
1 Scenarios (1 passed)
3 Steps (3 passed)
0m1.906s
 
 
Ran 2 tests containing 2 assertions in 12 msecs
0 failures, 0 errors.

7. Rozšíření testovacích scénářů – vyhledání uživatelů GitHubu

Nyní se zaměřme na nepatrně složitější testovací scénář. Budeme v něm na GitHubu vyhledávat uživatele podle jejich nicku (přezdívky) a zjišťovat jejich skutečné jméno a firmu, pro kterou pracují. Pro tento účel existuje v REST API GitHubu příslušný endpoint, takže implementace testovacích kroků bude poměrně jednoduchá. Nejprve si ukažme první verzi scénáře. Může vypadat takto:

Feature: GitHub API tests
 
  @smoketest
  Scenario: Check the GitHub API entry point
    Given GitHub is accessible
    When I access the API endpoint /
    Then I should receive response with 200 status code
 
  Scenario: Check the user search feature
    Given GitHub is accessible
    When I search for user with nick torvalds
    Then I should receive response with 200 status code
    Then I should receive proper JSON response
    Then I should find the user with full name Linus Torvalds
    Then I should find that the user works for company Linux Foundation

Ve skutečnosti se tímto způsobem většinou scénáře nepíšou, a to z toho důvodu, že série za sebou jdoucích vět začínajících slovem Then nevypadá příliš čitelně. Namísto toho je ale možné použít And s prakticky stejným významem (zavolají se naprosto stejné testovací kroky, samozřejmě pokud nedojde k pádu testu). Druhá verze testovacího scénáře tedy bude vypadat následovně:

Feature: GitHub API tests
 
  @smoketest
  Scenario: Check the GitHub API entry point
    Given GitHub is accessible
    When I access the API endpoint /
    Then I should receive response with 200 status code
 
  Scenario: Check the user search feature
    Given GitHub is accessible
    When I search for user with nick torvalds
    Then I should receive response with 200 status code
     And I should receive proper JSON response
     And I should find the user with full name Linus Torvalds
     And I should find that the user works for company Linux Foundation

8. Spuštění testovacích scénářů ve chvíli, kdy nejsou implementovány všechny požadované kroky

Pokud nyní, tj. bez dalších úprav projektu, testovací scénář spustíme, vypíšou se podle očekávání zprávy o tom, které kroky zatím nejsou definovány i s nápovědou, jak je můžeme definovat (ovšem nástroj Cucumber samozřejmě neví, které části popisu testovacích scénářů obsahují proměnné, takže nápověda není úplná):

$ lein cucumber
 
..U.UUUUU
 
2 Scenarios (2 undefined)
9 Steps (6 undefined, 3 passed)
0m2.152s
 
 
You can implement missing steps with the snippets below:
 
(Then #"^I should receive response with (\d+) status code$" [arg1]
  (comment  Write code here that turns the phrase above into concrete actions  )
  (throw (cucumber.api.PendingException.)))
 
(When #"^I search for user with nick torvalds$" []
  (comment  Write code here that turns the phrase above into concrete actions  )
  (throw (cucumber.api.PendingException.)))
 
(Then #"^I should receive proper JSON response$" []
  (comment  Write code here that turns the phrase above into concrete actions  )
  (throw (cucumber.api.PendingException.)))
 
(Then #"^I should find the user with full name Linus Torvalds$" []
  (comment  Write code here that turns the phrase above into concrete actions  )
  (throw (cucumber.api.PendingException.)))
 
(Then #"^I should find that the user works for company Linux Foundation$" []
  (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 3: Spuštění testů ve chvíli, kdy ještě nejsou definovány všechny testovací kroky.

9. Vytvoření projektu s novými závislostmi

Implementaci výše uvedených kroků testovacího scénáře vytvoříme v dnešním třetím demonstračním projektu. Základní struktura tohoto projektu je shodná s projektem předchozím, ovšem další úpravy již budou odlišné:

├── bdd
├── doc
│   └── intro.md
├── features
│   ├── github.feature
│   └── step_definitions
│       └── github_steps.clj
├── LICENSE
├── project.clj
├── README.md
└── resources
 
4 directories, 7 files

První podstatná změna se týká projektového souboru „project.clj“. Musíme do něj totiž přidat novou knihovnu „org.clojure/data.json“, a to pochopitelně z toho důvodu, že REST API GitHubu používá pro přenos údajů formát JSON:

(defproject cucumber+expect9 "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"]
                   [org.clojure/data.json "0.2.5"]
                   [expectations "2.0.9"]]
    :plugins [[com.siili/lein-cucumber "1.0.7"]
              [lein-expectations "0.0.8"]]
    :cucumber-feature-paths ["features/"]
    :target-path "target/%s"
    :profiles {:uberjar {:aot :all}
               :dev {:dependencies [[com.siili/lein-cucumber "1.0.7"]]}})

10. Implementace nových kroků vyžadovaných testovacími scénáři

Nyní budeme implementovat kód jednotlivých testovacích kroků. První část již známe, protože jsme se s ní seznámili v předchozích kapitolách. Pouze budeme muset doplnit import knihovny clojure.data/json:

(require '[expectations :refer [expect]])
(require '[clojure.data.json :as json])
 
 
(def URL "https://github.com/")
(def API-URL "https://api.github.com")
 
(def context (atom {:response nil}))
 
(System/setProperty "https.protocols" "TLSv1,TLSv1.1,TLSv1.2")
 
 
(defn request
    [address]
    (let [url        (new java.net.URL address)
          connection (.openConnection url)]
          (.connect connection)
          {:status  (.getResponseCode connection)
           :content (slurp (.getInputStream connection))}))
 
 
(Given #"^GitHub is accessible$"
    []
    (let [response (request URL)]
        (expect (:status response) 200)))
 
 
(When #"^I access the API endpoint (.+)$"
    [endpoint]
    (let [response (request (str API-URL endpoint))]
        (swap! context assoc :response response)))

Následuje implementace nových kroků. Nejdříve se jedná o krok, v němž se pokusíme vyhledat uživatele podle jeho nicku (přezdívky). Je to snadné, protože postačuje do REST API endpointu „/users/“ poslat nick a měl by se vrátit JSON s podrobnějšími informacemi o tomto uživateli. Pro jistotu omezíme množinu znaků použitých pro zápis nicku. Odpověď, kterou získáme, uložíme bez dalšího zpracování do kontextu:

(When #"^I search for user with nick ([A-Za-z0-9]+)$"
    [nick]
    (let [response (request (str API-URL "/users/" nick))]
        (swap! context assoc :response response)))

Další krok s kontrolou návratového kódu již známe:

(Then #"^I should receive response with (\d+) status code$"
    [code]
    (let [expected_code (Integer/parseInt code)
          actual_code   (-> @context :response :status)]
          (expect expected_code actual_code)))

Následuje test, který zjišťuje, zda se v těle odpovědi skutečně nachází data ve formátu JSON. V tomto případě se pouze pokusíme o parsing dat se zahozením výsledku (parsing není „línou“ funkcí, takže by se skutečně měl provést):

(Then #"^I should receive proper JSON response$"
    []
    (let [content (-> @context :response :content)]
        (json/read-str content)))

Poslední dva testovací kroky zjišťují informace uložené pod klíči „name“ a „company“ v datové struktuře získané z JSON dat. Připomeňme si, že funkci get je možné použít i ve chvíli, kdy příslušný klíč neexistuje:

(Then #"^I should find the user with full name (.+)$"
    [full-name]
    (let [content (-> @context :response :content json/read-str)]
        (expect (get content "name") full-name)))
 
 
(Then #"^I should find that the user works for company (.+)$"
    [company-name]
    (let [content (-> @context :response :content json/read-str)]
        (expect (get content "company") company-name)))

Poznámka: efektivnější by bylo uložit výsledek parsingu JSONu do kontextu již v kroku „I should receive proper JSON response“.

11. Spuštění testovacích scénářů kompletního projektu

Nyní je již vše připravené pro spuštění nového testovacího scénáře, takže si ho zkusme spustit:

$ lein cucumber
 
Running cucumber...
Looking for features in:  [/home/tester/temp/clojure/clojure-examples/cucumber+expect9/features]
Looking for glue in:  [/home/tester/temp/clojure/clojure-examples/cucumber+expect9/features/step_definitions]
.........
 
2 Scenarios (2 passed)
9 Steps (9 passed)
0m2.418s
 
 
Ran 4 tests containing 4 assertions in 15 msecs
0 failures, 0 errors.

Obrázek 4: Spuštění kompletního testovacího scénáře.

12. Vylepšení testovacího scénáře – vyhledání informací o větším množství uživatelů

Připomeňme si, že jednou z velmi zajímavých možností, jakými je možné testovací 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. Takovou tabulku si samozřejmě můžeme připravit i pro test s vyhledáním uživatelů na GitHubu:

Nick Plné jméno Firma/organizace
torvalds Linus Torvalds Linux Foundation
brammool Bram Moolenaar Zimbu Labs
tisnik Pavel Tišnovský Red Hat, Inc.

(nemohl jsem odolat a přidal jsem se za oba velikány :-) Je tomu tak z toho důvodu, že potřebujeme otestovat i znaky s nabodeníčky)

Testovací scénář se již upraví snadno. Jen si připomeňme, že se musí použít Scenario Outline a ty části vět z testů, které se mají nahradit hodnotami z tabulky, se musí uzavřít do úhlových závorek:

Feature: GitHub API tests
 
  @smoketest
  Scenario: Check the GitHub API entry point
    Given GitHub is accessible
    When I access the API endpoint /
    Then I should receive response with 200 status code
 
  Scenario: Check the user search feature
    Given GitHub is accessible
    When I search for user with nick torvalds
    Then I should receive response with 200 status code
     And I should receive proper JSON response
     And I should find the user with full name Linus Torvalds
     And I should find that the user works for company Linux Foundation
 
  Scenario Outline: Check the user search feature, perform the search for more users
    Given GitHub is accessible
    When I search for user with nick <nick>
    Then I should receive response with 200 status code
     And I should receive proper JSON response
     And I should find the user with full name <fullname>
     And I should find that the user works for company <company>
 
     Examples: users
     |nick|fullname|company|
     |torvalds|Linus Torvalds|Linux Foundation|
     |brammool|Bram Moolenaar|Zimbu Labs|
     |tisnik|Pavel Tišnovský|Red Hat, Inc.|

13. Opětovné spuštění testovacích scénářů

Po spuštění takto upravených testů získáme již očekávané výsledky. Pouze bych upozornil na celkový počet kroků, který zde dosahuje hodnoty 27. Je tomu tak pochopitelně z toho důvodu, že se všechny kroky v posledním scénáři opakují třikrát, pokaždé pro jiný nick:

$ lein cucumber
 
Running cucumber...
Looking for features in:  [/home/tester/temp/clojure/clojure-examples/cucumber+expect9/features]
Looking for glue in:  [/home/tester/temp/clojure/clojure-examples/cucumber+expect9/features/step_definitions]
.........
 
...........................
 
5 Scenarios (5 passed)
27 Steps (27 passed)
0m5.025s
 
 
Ran 4 tests containing 4 assertions in 13 msecs
0 failures, 0 errors.

14. Malá úprava testů pro uživatele GitHubu bez vyplněné společnosti

Někteří uživatelé přihlášení na GitHubu nemají vyplněnou společnost. To je kupodivu případ i Riche Hickeyho – autora programovacího jazyka Clojure i dalších zajímavých projektů. Nejdříve tedy Riche přidáme do tabulky v testovacím scénáři:

Feature: GitHub API tests
 
  @smoketest
  Scenario: Check the GitHub API entry point
    Given GitHub is accessible
    When I access the API endpoint /
    Then I should receive response with 200 status code
 
  Scenario: Check the user search feature
    Given GitHub is accessible
    When I search for user with nick torvalds
    Then I should receive response with 200 status code
     And I should receive proper JSON response
     And I should find the user with full name Linus Torvalds
     And I should find that the user works for company Linux Foundation
 
  Scenario Outline: Check the user search feature, perform the search for more users
    Given GitHub is accessible
    When I search for user with nick
    Then I should receive response with 200 status code
     And I should receive proper JSON response
     And I should find the user with full name
     And I should find that the user works for company
 
     Examples: users
     |nick|fullname|company|
     |torvalds|Linus Torvalds|Linux Foundation|
     |brammool|Bram Moolenaar|Zimbu Labs|
     |richhickey|Rich Hickey||
     |tisnik|Pavel Tišnovský|Red Hat, Inc.|

A následně nepatrně upravíme příslušný testovací krok takovým způsobem, aby se vrátil prázdný řetězec pro ty uživatele GitHubu, kteří nemají firmu vyplněnou. Můžeme využít toho, že funkci get lze předat třetí nepovinný parametr s výchozí hodnotou:

(Then #"^I should find that the user works for company (.*)$"
    [company-name]
    (let [content (-> @context :response :content json/read-str)]
        (expect (get content "company" "") company-name)))

15. Testovací scénář použitelný pro libovolné REST API, nejenom pro GitHub

Na závěr provedeme ještě jedno vylepšení testovacího scénáře. Do věty začínající na Given vložíme jak jméno testované služby, tak i její adresu. Tyto informace se tedy budou moci stát součástí kontextu testů (viz zvýrazněné části obsahující modifikovatelný text):

Feature: GitHub API tests
 
  @smoketest
  Scenario: Check the GitHub API entry point
    Given REST API for GitHub service is accessible on URL https://api.github.com
    When I access the API endpoint /
    Then I should receive response with 200 status code
 
  Scenario: Check the user search feature
    Given REST API for GitHub service is accessible on URL https://api.github.com
    When I search for user with nick torvalds
    Then I should receive response with 200 status code
     And I should receive proper JSON response
     And I should find the user with full name Linus Torvalds
     And I should find that the user works for company Linux Foundation
 
  Scenario Outline: Check the user search feature, perform the search for more users
    Given REST API for GitHub service is accessible on URL https://api.github.com
    When I search for user with nick
    Then I should receive response with 200 status code
     And I should receive proper JSON response
     And I should find the user with full name
     And I should find that the user works for company
 
     Examples: users
     |nick|fullname|company|
     |torvalds|Linus Torvalds|Linux Foundation|
     |brammool|Bram Moolenaar|Zimbu Labs|
     |richhickey|Rich Hickey||
     |tisnik|Pavel Tišnovský|Red Hat, Inc.|

16. Úprava kroků testu

Začátek skriptu s testovacími kroky se bude muset nepatrně změnit, protože budeme potřebovat do kontextu přidat i URL testované služby (klidně si můžeme přidat i její jméno):

(require '[expectations :refer [expect]])
(require '[clojure.data.json :as json])
 
 
(def context (atom {:response nil
                    :api-url nil}))

Následuje původní část:

(System/setProperty "https.protocols" "TLSv1,TLSv1.1,TLSv1.2")


(defn request
    [address]
    (let [url        (new java.net.URL address)
          connection (.openConnection url)]
          (.connect connection)
          {:status  (.getResponseCode connection)
           :content (slurp (.getInputStream connection))}))

V kroku Given si zapamatujeme URL služby a následovně se ji pokusíme zkontaktovat (většina REST API služeb by měla zareagovat buď kódem 200 nebo 3×x pro přesměrování):

(Given #"^REST API for ([A-Za-z]+) service is accessible on URL (.*)$"
    [service url]
    (swap! context assoc :api-url url)
    (let [api-url  (:api-url @context)
          response (request (:api-url @context))]
        (expect (:status response) 200)))

Další dva kroky již budou využívat URL uložené do kontextu:

(When #"^I access the API endpoint (.+)$"
    [endpoint]
    (let [api-url  (:api-url @context)
          response (request (str api-url endpoint))]
        (swap! context assoc :response response)))
 
 
(When #"^I search for user with nick ([A-Za-z0-9]+)$"
    [nick]
    (let [api-url  (:api-url @context)
          response (request (str api-url "/users/" nick))]
        (swap! context assoc :response response)))

Zajímavé je, že všechny další kroky (Then, And) již URL nepotřebují a tedy se ani jejich kód žádným způsobem nemusel modifikovat:

(Then #"^I should receive response with (\d+) status code$"
    [code]
    (let [expected_code (Integer/parseInt code)
          actual_code   (-> @context :response :status)]
          (expect expected_code actual_code)))
 
 
(Then #"^I should receive proper JSON response$"
    []
    (let [content (-> @context :response :content)]
        (json/read-str content)))
 
 
(Then #"^I should find the user with full name (.+)$"
    [full-name]
    (let [content (-> @context :response :content json/read-str)]
        (expect (get content "name") full-name)))
 
 
(Then #"^I should find that the user works for company (.*)$"
    [company-name]
    (let [content (-> @context :response :content json/read-str)]
        (expect (get content "company" "") company-name)))

17. Repositář s demonstračními příklady

Všech šest demonstračních příkladů a projektů určených pro programovací jazyk 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:

18. 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. Použití jazyka Gherkin při tvorbě testovacích scénářů pro aplikace psané v Clojure
    https://www.root.cz/clanky/pouziti-jazyka-gherkin-pri-tvorbe-testovacich-scenaru-pro-aplikace-psane-v-nbsp-clojure/

19. Odkazy na Internetu

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