среда, 14 июня 2017 г.

Event Sourcing part 1

Spacemacs + smartscan

Very handy package - Smartscan for Clojure code editing, renaming local vars in fn, jumping to usages & more.
https://github.com/mickeynp/smart-scan


Spacemacs + Cider + 2 repls

When we use EMACS/Spacemacs and want to use two repls for CLJ and CLJS then
1. just add (add-to-list 'auto-mode-alist '("\.cljs$" . clojurescript-mode)) to init.el to enable clojurescript-mode for cljs files (otherwise it will be clojure mode with some problems in CLJS).
2. Select CLJ file and run cider-jack-in to run Clojure REPL
3. Select CLJS file (check that EMACS in Clojurescript mode) and run cider-jack-in-clojurescript or run cider-jack-in and then  (require'[ figwheel-sidecar.repl-api :as f]) and
(f/start-figwheel!) and (f/cljs-repl)

If I need to work with two different Clojure REPLs and eval S-forms I do following.

Run two REPLs by cider-jack-in command. 

To switch between REPL connections:
1. M-x and choose cider-toogle-request-dispatch to set CIDER variable to static mode
2. choose appropriate repl window and run command: cider-make-connection-default

Now, all S-exp will be evaluated in a default repl process.

воскресенье, 2 апреля 2017 г.

clojure.test tutorial

(ns example.basic-types-tests
  (:gen-class)
  (:require [clojure.test :refer [deftest testing is use-fixtures]]
            [example.basic-types :as basic]
            [clojure.spec :as s]
            [clojure.spec.gen :as gen]))


(defn once-fixture [f]
  (println "Prepare for for all tests...")
  (f)
  (println "Clean up after all tests..."))

(defn before-any-test-fixture [f]
  (println "Next test preparation...")
  (f)
  (println "Clean up after test..."))

(use-fixtures :once once-fixture)
(use-fixtures :each before-any-test-fixture)

(deftest basic-types-spec
  (testing "testing basic types spec"
    (is (= 1 1))))

(deftest basic-types-generator-test
  (testing "testing generators for basic types"
    (is (= 1000 (count (gen/sample (s/gen ::basic/string) 1000))))))

(deftest arithmetic-test
  (testing "this should throw exception"
    (is (thrown? ArithmeticException (/ 1 0)))))


;; This will make tests hook: only specified tests will be run.
;; Warining! if hook was installed then only specified tests will be run
;; to disable hook perform (ns-unmap *ns* 'test-ns-hook)
;(defn test-ns-hook []
;  (basic-types-generator-test))

;; To run particular test form some namespace use follwing command in repl:
;;(clojure.test/test-vars [#'example.basic-types-tests/arithmetic-test])
;; if everything ok it returns nil, otherwise returns report. This will run test with existing fixtures
;; or lein test :only my.namespace/my-test

суббота, 1 апреля 2017 г.

clojure.test with clojure.spec

First of all, add necessary dependency to project.clj
  [org.clojure/test.check "0.9.0"]

Then add following line to project.clj  :monkeypatch-clojure-test false due to bug https://github.com/technomancy/leiningen/issues/2173

Then, define fns and their spec

(require '[clojure.spec :as s])

(defn average [list-sum list-count]
  (/ list-sum list-count))

(s/fdef average
  :args (s/and (s/cat :list-sum float? :list-count integer?)
               #(not (zero? (:list-count %))))
  :ret number?)


Then in test folder make file with tests and put

(:require [clojure.test :as t]
            [clojure.string :as str]
            [example.core :refer :all]
            [clojure.spec :as spec]
            [clojure.pprint :as pprint]
            [clojure.spec.test :as stest])

(defmacro defspec-test
  ([name sym-or-syms] `(defspec-test ~name ~sym-or-syms nil))
  ([name sym-or-syms opts]
   (when t/*load-tests*
     `(def ~(vary-meta name assoc :test `(fn []
                                           (let [check-results# (clojure.spec.test/check ~sym-or-syms ~opts)
                                                 checks-passed?# (every? nil? (map :failure check-results#))]
                                             (if checks-passed?#
                                               (t/do-report {:type    :pass
                                                             :message (str "Generative tests pass for "
                                                                           (str/join ", " (map :sym check-results#)))})
                                               (doseq [failed-check# (filter :failure check-results#)
                                                       :let [r# (clojure.spec.test/abbrev-result failed-check#)
                                                             failure# (:failure r#)]]
                                                 (t/do-report
                                                   {:type     :fail
                                                    :message  (with-out-str (clojure.spec/explain-out failure#))
                                                    :expected (->> r# :spec rest (apply hash-map) :ret)
                                                    :actual   (if (instance? Throwable failure#)
                                                                failure#
                                                                (:clojure.spec.test/val failure#))})))
                                             checks-passed?#)))
        (fn [] (t/test-var (var ~name)))))))


(defspec-test average-test ['example.core/average] {:clojure.spec.test.check/opts {:num-tests 500000}})

Notice that testing fn is fully qualified and ' at the begining in vector.

That's it.


Update:

Also, we can write usual test. To test function example.core/average with input auto generation and output validation with spec we can do it followin way:

(t/deftest average-test2
  (t/testing "average test"  
    (let [r (stest/check `example.core/average {:clojure.spec.test.check/opts {:num-tests 1000}})]
        (t/is (= true (-> r first :clojure.spec.test.check/ret :result))))))

Update 2:

To test function example.core/average we need to require [clojure.test.check.clojure-test :as check.clj-test :refer [defspec]]

(defspec average-test3   10   (prop/for-all [r (gen/tuple gen/int gen/int)]
                                                    (number? (apply example.core/average r))))

Update 3:
To run reproducible test we need to require [clojure.test.check.clojure-test :as check.clj-test :refer [defspec]]

(defspec abcd {:seed 12345 :num-tests 1000}
         (prop/for-all [s (s/gen ::basic/blank-string)]
                       (s/valid? ::basic/blank-string s)))

Update 4:

In example.core namespace:

(defn my-div [x y]
  "divide two numbers"
  (/ x y))

(s/fdef my-div
  :args (s/and (s/cat :first number? :second number?)
               #(not (zero? (:second %))))
  :ret number?)

In tests namespace:

(deftest my-div-test
  (let [result (spec.test/check `example.core/my-div {:clojure.spec.test.check/opts {:num-tests 1000}})]
    (is (= true (-> result first :clojure.spec.test.check/ret :result)))
    (clojure.spec.test/instrument 'example.core/my-div)
    (is (thrown? clojure.lang.ExceptionInfo (example.core/my-div 1 0)))
    (clojure.spec.test/unstrument example.core/my-div)
    (is (thrown? ArithmeticException (example.core/my-div 1 0)))))




четверг, 16 марта 2017 г.

clojure queue FIFO

If you need simple FIFO queue you can use clojure.lang.PersistentQueue/EMPTY function with some wrappers on it.

;; ------------------------------------------------------------------
  (defn new-queue []
    (atom clojure.lang.PersistentQueue/EMPTY))

  (defn push-queue [queue value]
    (swap! queue conj value))

  (defn pop-queue [queue]
    (let [value (peek @queue)]
      (swap! queue pop)
      value))
;; ------------------------------------------------------------------

Now let's test it

  (def a (new-queue))
;; => nil

(push-queue a 1)
;; => #object...

(push-queue a 2)
;; => #object...

(push-queue a 3)
;; => #object...

(push-queue a "hello")
;; => #object...

Let's check a content of queue a:

(seq @a)
;; => (1 2 3 "hello")

Now time to take first item:

(pop-queue a)
;; => 1

(seq @a)
;; => (2 3 "hello")

понедельник, 13 марта 2017 г.

emacs interactive package install

(use-package company
:ensure t
:bind (("C-c /". company-complete))
:config
(global-company-mode))