Spec Oracle
In this short article I am going to talk about clojure, more specifically clojure.spec I assume a basic understanding of clojure.spec, check out its rationale and guide if that assumption doesn't hold for you.:
xxxxxxxxxx
(ns spec-oracle
(:require [clojure.spec :as s]
[clojure.spec.test :as stest]))
xthe evaluation will appear here (soon)...
So the scenario is that you were told to write a function which acts like reverse from
clojure.core but isn't reverse from
clojure.core and being a dude
you are, you happily abided, naming it
my-reverse rather affectionately:
xxxxxxxxxx
(defn my-reverse [input-seq]
(into () input-seq))
xxxxxxxxxx
the evaluation will appear here (soon)...
But you didn't want to lie in bed at night, being bugged by bugs in
my-reverse
so you decided to write a specification for it:
(s/fdef my-reverse
;; input-seq should satisfy seq?
:args (s/cat :input-seq seq?)
;; return value should satisfy seq?
:ret seq?
;; return value should be equal to the
;; reverse of the input-seq
:fn #(= (:ret %) (reverse (->
%
:args
:input-seq))))
Now, you don't need to test your function, you can let your computer do it for you using property based generative testing:
;; function works as specified?
(stest/check `my-reverse)
({:spec #object[clojure.spec.alpha$fspec_impl$reify__1215 0x3a051bbc
"clojure.spec.alpha$fspec_impl$reify__1215@3a051bbc"],
:clojure.spec.test.check/ret {:result true, :num-tests 1000, :seed 1498864073447},
:sym spec-playground.core/my-reverse})
So the :result was true,
my-reverse works as specified. You have done something special here but you don't realize it yet, let me
explain:
You took a system you knew worked perfectly, and used it to test a rewrite of the same system. In our case the
already
well-tested system was reverse from clojure.core and the rewritten system was my-reverse.
If you still don't see what the big deal is, allow me to put on my thought leader hat for a minute and show you
that the
possibilities are indeed limitless:
You can optimize that function prematurely, proving that the fears of premature optimization were largely
unfounded.
You can rewrite that legacy system, establishing your legacy instead. You can finally design your own sorting
algorithm,
timsort is an alright name but <insert_your_name_here>sort has a much better ring to it, don't you
think?
So we took a pattern commonly known as
Test Oracle, put it on steroids using
clojure.spec and
clojure/test.check, and called it
Spec Oracle just for the kicks. Have fun using it!