mail-alt git-squared

~/abhirag

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.:

    (ns spec-oracle
  (:require [clojure.spec :as s]
            [clojure.spec.test :as stest]))

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:

    (defn my-reverse [input-seq]
  (into '() input-seq))

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 
  "[email protected]"], 
  :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!