Zobrazují se příspěvky se štítkemleiningen. Zobrazit všechny příspěvky
Zobrazují se příspěvky se štítkemleiningen. Zobrazit všechny příspěvky

12. dubna 2012

Leiningen, jak nemít vlasy v ohni

Leiningen je buildovací a projektový nástroj pro Clojure, který se velmi silně inspiroval Javovským Mavenem nebo Groovyovským Gradle. Jeho podtitulem je "automating Clojure projects without setting your hair on fire". Po vlastních zkušenostech musím říct, že podtitul je zvolen velmi trefně. Leiningen bych rozhodně doporučil, jako startovací bod pro seznamování se s Clojure.

Pravdou je, že prvotní rozchození Clojure je tvrdým oříškem i pro zkušené hackery a pro nováčky může být trochu stresující. (OK, tak pro ty z nás, kteří jsou opravdu dobří, to zas takový problém není ;-)

Instalace

Instalace na linuxu je triviální (viz README):
  1. stáhnne se skript,
  2. přidá se do $PATH,
  3. udělá se spustitelným (chmod 755 lein).
Na Windows je to o něco málo složitejší (opět, viz README).

Korektní instalaci ověříme příkazem lein version:
Leiningen 1.7.1 on Java 1.7.0_147-icedtea OpenJDK 64-Bit Server VM

REPL

REPL bývá silnou zbraní skriptovacích jazyků. Nejinak je tomu i v Clojure. Pokud máme nainstalovaný Leiningen, můžeme rovnou začít používat Clojure (bez jeho instalace) příkazem:
lein repl

REPL ukončíme buď klávesovou zkratkou Ctrl-C, nebo příkazem:
(System/exit 0)

První projekt

Leiningen umožňuje vytvořit kostru nového projektu pomocí příkazu:
lein new <project>

Vytvoření a layout projektu 

V layoutu projektu vidíme klasické rozdělení na zdrojový/produkční kód a testy (adresáře src a test) a také soubor s definicí projektu project.clj.

Pokud chceme mít v projektu jiné namespacy, než je název projektu můžeme použít pro vytvoření projektu syntaxi:
lein new project directory

Projekt s jiným namespacem

Definice projektu

Konfiguračním souborem pro projekt (a Leiningen) je project.clj, jehož iniciální podoba je na následujícím výpisu:
(defproject my-project "1.0.0-SNAPSHOT"
  :description "FIXME: write description"
  :dependencies [[org.clojure/clojure "1.3.0"]])
Nejzajímavější položkou je keyword :dependencies, které (překvapivě) definuje závislosti projektu. Podobně jaku v Mavenu jsou závislosti stahovány z repository - defaultními úložišti jsou Clojars a Maven Central. Další repository lze přidat pomocí keywordu :repositories.
:repositories {
  "my-local" "http://my-local/repository"
  "java.net" "http://download.java.net/maven/2"}
Pokud chceme použít některé závislosti pouze při vývoji, použijeme pro jejich definici keyword :dev-depencencies. Já ho používám např. pro Midje.
:dev-dependencies [[midje "1.3.2-SNAPSHOT"]
                   [lein-midje "1.0.8"]]
Pokud budeme výsledný projekt distribuovat jako spustitelný JAR soubor, hodí se nám keyword :main, které definuje namespace, ve kterém se nachází funkce -main (která bude defaultně zavolána při spuštění lein run (viz dále)); a také se zpropaguje do položky Main-Class v MANIFEST.MF.

Když už jsme u manifestu, existuje i keyword :manifest, které umožní vložit specifické položky do souboru MANIFEST.MF.
:main blog-lein.core
:manifest {"Build-Version" ~project-version
           "Built-Time" ~(str (Date.))}

Další tasky

Kompletní seznam tasků Leiningenu lze vypsat příkazem:
lein help

Ty které jsou shodné s Mavenem není potřeba představovat/vysvětlovat:
  • clean
  • compile
  • deploy
  • install
  • test
Z těch ostatních jsou zajímavý:
  • classpath - vypíše classpath aktuálního projektu.
  • deps - dotáhne z repozitářů závislosti definované v :dependencies a :dev-dependencies. Defaultní umístění je adresář lib, ev. lib/dev a dá se ovlivnit keywordem :library-path.
  • pom - vytvoří z project.clj soubor pom.xml pro Maven.
  • repl - spustí REPL, buď samostatně (standalone), nebo v rámci projektu - v tom případě přidá do classpath všechny knihovny definované v :dependencies.
  • run - spustí -main funkci projektu. Namespace funkce musí být definován keywordem :main.
  • search - prohledá repozitáře a vypíše dostupné závislosti.
  • uberjar - zabalí všechny soubory a závislosti do jednoho JAR souboru. Vhodné pro standalone distribuci. Pokud má být JAR spustitelný, musí namespace s funkcí -main obsahovat ve své deklaraci direktivu :gen-class.

Zkušební projekt

Pro studijní důvody jsem vytvořil jednoduchý projekt, na kterém se dají vyzkoušet výše uvedené příkazy (samozřejmě kromě lein new):
[Update]Projekt je k dispozici na Bitbucketu: blog-lein[/Update]

Projekt má následující definici (soubor project.clj):
(import java.util.Date)
(def project-version "1.0.0-SNAPSHOT")
(defproject blog-lein project-version
  :description "Sometimes Clojure, Leiningen"
  :dependencies [[org.clojure/clojure "1.3.0"]]
  :dev-dependencies [[midje "1.3.2-SNAPSHOT"]
                     [lein-midje "1.0.9"]]
  :main blog-lein.core
  :manifest {"Build-Version" ~project-version
             "Built-Time" ~(str (Date.))})
Doporučoval bych vyzkoušet si:
  • lein classpath
  • lein jar (a mrknout do MANIFEST.MF)
  • lein pom
  • lein repl (a zkusit si nějaké Midje funkce)
  • lein run
  • lein uberjar (a spustit si kód pomocí java -jar <jarfile>.jar)
Věřím, že po tomto malém úvodu se vám bude s Clojure začínat mnohem jednoduššeji (a nemít přitom vlasy v jednom ohni ;-)

2. dubna 2012

Lepší testování v Clojure. Midje

Dneska navážu na unit testování v Clojure, nicméně dostanu se k tomu oklikou. Minulé léto se celkem hojně psalo o desátém výročí deklarace Manifesto for Agile Software Development. Internetem (a mojí čtečkou) tehdy protekly nějaké rozhovory s někdejšími účastníky/signatáři. Jedním z nich byl i Brian Marick, autor Midje, testovacího frameworku pro Clojure (ano, to je to Midje, které jsem zmiňoval ve svém postu o ThoughtWorks Radar).

Instalace/zprovoznění

Midje je nejjednodušší začít používat pomocí Leiningenu (určitě jeden z příštích zápisků). Definice projektu může vypadat třeba takto:
(defproject blog-midje "1.0.0-SNAPSHOT"
            :description "Midje for Sometimes Clojure"
            :dependencies [[org.clojure/clojure "1.3.0"]]
            :dev-dependencies [[midje "1.3.2-SNAPSHOT"]])
REPL se pak spustí příkazem lein repl.

Fakta

Základem testování v Midje jsou fakta. Fakta o budoucím kódu (test first):
(ns blog-midje (:use midje.sweet))
; nil
(fact (+ 1 1) => 2)
; true
(fact 1 => odd?)
; true
(fact "truth about one" 1 => even?)
; FAIL at (NO_SOURCE_FILE:11)
; Actual result did not agree with the checking function.
;         Actual result: 1
;     Checking function: even?
; false
Fakt může mít více klauzulí. Dá se použít makro fact nebo facts:
(facts "about42"
       (+ 21 21) => 42
       (* 6 7) => 42)
; true
Pokud je potřeba fakt prezentovat pomocí sady hodnot, nabízí Midje tzv. tabulková fakta:
(defn xor [x y]
  (if (= x y) false true))
; #'blog-midje/xor

(tabular
  (fact "The rules of XOR"
        (xor ?x ?y) => ?expected)
  ?x    ?y    ?expected
  0     0     false
  0     1     true
  1     0     true
  1     1     false
  false false false
  false true  true
  true  false true
  true  true  false)
; true
Vyhození výjimky se dá otestovat pomocí funkce (checkeru) throws:
(fact (/ 42 0) => (throws ArithmeticException))
; true

Spuštění testů

Midje předpokládá pro spuštění (všech) testů nějaký nástroj, jako např. Leiningen nebo Cake. Pojďme se podívat, jak by vypadalo rozchození projektu a testů pomocí Leiningenu. Nový projekt vytvoříme příkazem:
lein new blog-midje

Struktura projektu by měla vypadat takto:

Layout projektu

Do definice projektu (project.clj) je potřeba přidat závislosti. Kromě těch obligátních je to hlavně knihovna lein-midje:
(defproject blog-midje "1.0.0-SNAPSHOT"
            :description "Midje for Sometimes Clojure"
            :dependencies [[org.clojure/clojure "1.3.0"]]
            :dev-dependencies [[midje "1.3.2-SNAPSHOT"]
                               [lein-midje "1.0.8"]])
Prvně napíšeme test (soubor test/blog_midje/test/core.clj):
(ns blog-midje.test.core
  (:use [blog-midje.core])
  (:use [midje.sweet]))

(tabular
  (fact "The rules of XOR"
        (xor ?x ?y) => ?expected)
  ?x    ?y    ?expected
  0     0     false
  0     1     true
  1     0     true
  1     1     false
  false false false
  false true  true
  true  false true
  true  true  false)
Potom napíšeme testovanou funkci (src/blog_midje/core.clj):
(ns blog-midje.core)

(defn xor
    "Returns true if x and y are mutually exclusive."
    [x y]
    (if (= x y) false true))
Testy potom spustíme příkazem lein test, nebo lépe lein midje:

Spuštění testů

Celý projekt je ke stažení zde: blog-midje.zip.

[Update]Celý projekt je dispozici na Bitbucketu: blog-midje[/Update]

To je pro dnešek vše. Pokud se chcete podívat na úvod do Midje od samotného Briany Maricka, zkuste  videa uvedená na wiki stránce Midje.


15. srpna 2011

Testování v Clojure

Ačkoliv Rich Hickey říká, že nepíše unit testy, přece jenom do Clojure zahrnul "framework" pro unit testy. Já sám, naopak, jsem již léty TDD infikován a testy píšu rád. Takže, jak se to dělá v Clojure? Nejprve natáhneme knihovnu clojure.test:
(ns my-tests (:use clojure.test))
; nil

Aserce (tvrzení)

Základem testů je makro is:
(is (= 2 (+ 1 1)))
; true
(is (odd? 1))
; true
(is (even? 1) "truth about one")
; 
; FAIL in clojure.lang.PersistentList$EmptyList@1 (NO_SOURCE_FILE:30)
; truth about one
; expected: (even? 1)
;   actual: (not (even? 1))
; false
Více assertions/parametrů se dá testovat pomocí makra are:
(are [x y] (= 42 (+ x y))
     21 21
     20 22
     42 0)
; true
Vyhození výjimky se dá otestovat pomocí funkce thrown?:
(is (thrown? ArithmeticException (/ 42 0)))
; #<ArithmeticException java.lang.ArithmeticException: Divide by zero>
(is (thrown? ArithmeticException (/ 42 42)))
; 
; FAIL in clojure.lang.PersistentList$EmptyList@1 (NO_SOURCE_FILE:48)
; expected: (thrown? ArithmeticException (/ 42 42))
;   actual: nil
; nil

Definice testů

OK, to byly tvrzení (assertions). Jak definuji test? Jedna z možností je makro with-test:
(with-test
    (defn add [x y]
        (+ x y))
    (is (= 42 (add 21 21)))
    (is (= 42 (add 20 22)))
    (is (= 42 (add 42 0))))
Druhou možností je použít makro deftest. Jeho výhodou je, že vytváří funkci (v našem případě ultimate-addition), kterou lze zavolat z libovolného namespace. To umožňuje mít testy v samostatném souboru tak, jak jsme z unit testů zvyklí.
(deftest ultimate-addition
    (is (= 42 (add 21 21)))
    (is (= 42 (add 20 22)))
    (is (= 42 (add 42 0))))

Spuštění testů

Máme definované dva testy (a šest asercí). Jak je spustíme? Testy v aktuálním namespace odpálíme funkcí run-tests:
(run-tests)
;
; Testing my-tests
;
; Ran 2 tests containing 6 assertions.
; 0 failures, 0 errors.
; {:type :summary, :pass 6, :test 2, :error 0, :fail 0}
Testy v jiném namespace spustíme obdobně:
(run-tests 'some.namespace 'some.other.namespace)

Automatizace testů

Tak. Konec srandiček! Velký kluci buildujou (a spouští testy) Leiningenem. Prvně vytvoříme projekt příkazem lein new lein-tests:

Vytvoření a layout projektu

Test first! Napíšeme testy (soubor core.clj v adresáři test):
(ns lein-tests.test.core
  (:use [lein-tests.core])
  (:use [clojure.test]))

(deftest split-line-test
  (is (= ["foo" "bar"]
         (split-line "foo;bar" #";")))
  (is (= ["foo" "bar"]
         (split-line "foo,bar" #", ?")))
  (is (= ["foo" "bar"]
         (split-line "foo, bar" #", ?"))))

(deftest get-phone-test
  (is (= "123456789"
         (get-phone "123456789;Guido")))
  (is (= ""
         (get-phone ";Guido"))))
A napíšeme (testované) funkce (soubor core.clj v adresáři src):
(ns lein-tests.core
  (:use [clojure.string :only (split)]))

(defn split-line [line separator]
  (split line separator))

(defn get-phone [line]
  (first (split-line line #";")))
No a na závěr testy spustíme příkazem lein test. Nádhera!

Spuštění testů

12. února 2011

Uzávěr v laboratoři (jak začít)

Dneska bych tady měl dvě "drobnosti" pro ty, kdo chtějí s Clojure začít. Prvně to kratší: Chcete zkusit Clojure a přitom nic neinstalovat, jen si vyzkoušet jazyk? Na stránce Try Clojure je k dispozici online REPL, včetně krátkého tutoriálu. Jako perlička - k dispozici je Google Chrome rozšíření try-clojure-in-chrome, které zmíněnou stránku využívá pro zobrazení REPLu v Chrome.

[Updated: Zdá se, že posledních pár dní je stránka Try Clojure mrtvá. :-/ ]

Hlavní věc, o které bych ale dnes chtěl psát, je výborný Clojure tutoriál labrepl. Tutoriál vytvořil Stuart Halloway, autor vůbec první knihy o Clojure Programming Clojure a také jeden z hlavních contributorů do Clojure samotného.

Musím říct, že labrepl mě vyloženě nadchnul a to jak obsahem, tak implementací. Celý tutoriál je totiž v Clojure jak napsaný (HTML je generovaný pomocí Clojure web frameworku Compojure), tak zbuildovaný (konrétně Leiningenem, o kterém bych chtěl něco napsat příště). Tutoriál se spustí jako REPL, přičemž v rámci něho na pozadí běží web server Jetty s HTML tutoriálem a v již spuštěném REPLu se dají zkoušet jednotlivé příklady. Tento neohrabaný popis asi není moc názorný, takže radši doporučuji přímo vyzkoušet.

Nebudu tady popisovat obsah tutoriálu, jen bych zmínil věc, na kterou jsem zatím online nenarazil. A to je dokumentace přímo v REPLu. To je další vlastnost, která mne nadchla - zatím jsem se nesetkal s jazykem (s částečnou výjimkou v Pythonu), který by tak snadno a v takovém rozsahu něco podobného poskytoval.

Dokumentace funkce:
(doc zipmap)
; -------------------------
; clojure.core/zipmap
; ([keys vals])
;   Returns a map with the keys mapped to the corresponding vals.

Vyhledání dokumentace:
(find-doc "zip")
; -------------------------
; clojure.zip/seq-zip
; ([root])
;   Returns a zipper for nested sequences, given a root sequence
; -------------------------
; clojure.zip/vector-zip
; ([root])
;   Returns a zipper for nested vectors, given a root vector
; -------------------------
; clojure.zip/xml-zip
; ([root])
;   Returns a zipper for xml elements (as from xml/parse),
;   given a root element

Funkce javadoc otevře stránku Javadocu v prohlížeči (buď lokální, nebo vzdálenou):
(javadoc java.util.Date)
; "http://java.sun.com/javase/6/docs/api/java/util/Date.html"
; Created new window in existing browser session.

A nakonec funkce, která je nejvíc cool! source zobrazí implementaci dané funkce:
(source drop)
; (defn drop
;   "Returns a lazy sequence of all but the first n items in coll."
;   {:added "1.0"}
;   [n coll]
;   (let [step (fn [n coll]
;                (let [s (seq coll)]
;                  (if (and (pos? n) s)
;                    (recur (dec n) (rest s))
;                    s)))]
;     (lazy-seq (step n coll))))