Clojure.Spec Workshop

Part 2 - Data generation

Part 2 - Data generation

```
(str (js/Date.))
```

- A mathematician
- A pragmatic theorist
- A coder
- A freak of interactivity
- Founded Audyx - an Audiology Startup with 30K LOCs in Clojurescript
- Author of KLIPSE
- A Clojure consultant

- Data generation based on specs
- Specing functions
- Testing functions

Open this presentation in your computer for a collaborative live coding session: http://slides.klipse.tech/clojure-spec-cr17/part1.html#slide-3

(It works also on mobile)

* The interactive code snippets are powered by KLIPSE.* ðŸ¤—

```
(map inc [1 2 3])
```

For the upcoming parts, we need to require two additional namespaces

```
(ns my.playground
(:require [clojure.test.check :as tc ]
[clojure.test.check.generators]
[clojure.test.check.properties :as prop :include-macros true]
[clojure.spec.alpha :as s]
[clojure.spec.test.alpha :as stest]
[clojure.spec.gen.alpha :as gen]))
```

We can generate data samples out of our specs:

```
(s/def ::big-integer (s/and integer?
#(> % 1000000)))
(s/def ::short-string (s/and string?
#(< (count %) 5)))
(s/def ::big-integer-or-short-string (s/or :int ::big-integer
:str ::short-string))
```

Either with `gen/sample`

:

```
(gen/sample (s/gen ::big-integer) 5)
```

Or with `s/exercise`

:

```
(s/exercise ::big-integer-or-short-string 5)
```

Why do we need to specify optional keys in a map?

```
(gen/sample (s/gen (s/keys :req [::big-integer])) 5)
```

```
(gen/sample (s/gen (s/keys :req [::big-integer] :opt [::short-string])) 5)
```

You can describe the shape of the args and return value of your functions

Let's imagine we have an addition function where the result is bounded:

```
(defn bounded-addition [a b {:keys [upper lower]}]
(cond-> (+ a b)
lower (max lower)
upper (min upper)))
```

Here are the initial specifications of our function:

```
(s/def ::upper number?)
(s/def ::lower number?)
(s/fdef bounded-addition
:args (s/cat :a number? :b number? :boundaries (s/keys :req-un [::upper ::lower])))
```

What happens when one passes the wrong arguments?

```
(stest/instrument `bounded-addition)
(bounded-addition 1 2 {})
```

You can `instrument`

and `unstrument`

:

```
(stest/unstrument `bounded-addition)
(bounded-addition 1 2 {})
```

What is the default - `instrument`

or `unstrument`

?

Let's check how our function behaves with valid random arguments

```
(s/exercise-fn bounded-addition)
```

We can also specify:

- The returned value -
*a number* - The relationships between different parts of the input -
*upper bound not smaller than lower bound* - The relationships between the input and the returned value -
*returned value between lower and upper bounds*

Let's imagine we have a multiplication function where the result is bounded:

```
(defn bounded-multiplication [a b {:keys [upper lower]}]
(-> (* a b)
#_(max lower)
#_(min upper)))
```

Here are the full specifications of our function:

```
(s/def ::upper number?)
(s/def ::lower number?)
(s/fdef bounded-multiplication
:args (s/and (s/cat :a number? :b number? :boundaries (s/keys :req-un [::upper ::lower]))
#(>= (-> % :boundaries :upper) (-> % :boundaries :lower)))
:ret number?
:fn #(<= (-> % :args :boundaries :lower) (:ret %) (-> % :args :boundaries :upper)))
```

Let's test our function (Test generation for free!):

```
(-> (stest/check `bounded-multiplication {::tc/opts {:num-tests 10}})
first
stest/abbrev-result)
```

What do you think about this amazing stuff?

- Clojure 1.9 introduces clojure.spec: tutorial with live coding examples
- spec Guide
- Creating a spec for destructuring
- Clojure.spec official API documentation
- Custom defn macro with clojure.spec - part 1: conform/unform
- Custom defn macro - part 2: playing with parse trees
- better-cond by Mark Engleberg
- KLIPSE - the interactive embeddable web repl

powered by KLIPSE /