Skip to content

Instantly share code, notes, and snippets.

@KingCode
Last active February 19, 2017 13:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save KingCode/ef2ac029dbcf2881ad1762611f837d56 to your computer and use it in GitHub Desktop.
Save KingCode/ef2ac029dbcf2881ad1762611f837d56 to your computer and use it in GitHub Desktop.
fizzbuzz example spec
(ns hellospec.fizzbuzz
(:require [clojure.spec :as s]
[clojure.spec.test :as stest]
;; to accomodate KLIPSE:
;; "[...]cljs.spec.gen.frequency is undefined[...]"
#?(:clj [clojure.spec.gen :as gen]
:cljs [clojure.test.check.generators :as gen])))
;; A fizzbuzz which doesn't use condition checking/branching
(defn fizzbuzz
"Generates n fizzbuzz sequence elements, with Fizz and Buzz for multiples of m1 respectively.
m1 and m2 must be positive integers and n non-negative, and m1 < m2."
([m1 m2 n]
(let [c #(->> % vector (concat (-> %2 dec (repeat nil))) cycle)
fb-re #"FizzBuzz|Fizz|Buzz"
format (fn [f b x]
(-> (re-matches fb-re (str f b))
vector
(conj x)))]
(->> [(c "Fizz" m1), (c "Buzz" m2), (next (range))]
(apply map #(some identity (format % %2 %3)))
(take n))))
([n]
(fizzbuzz 3 5 n)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; fizzbuzz argument generation
(def ^:dynamic *max-limit* 100)
(defn max-arg [] (* 2 *max-limit*))
(defn posint [& [pred]]
(s/gen (s/and ::zero+ (or pred (constantly true)))))
(defn fizzbuzz-args-gen []
(gen/frequency
;;1-arg, limit for basic 3,5 fizzbuzz
[[1 (gen/fmap vector (s/gen (s/and int? pos? #(< % *max-limit*))))]
;;3-arg: must have fizz <= buzz
[19
(gen/fmap (fn [[x y z]]
(-> (sort [x y])
vec
(conj z)))
(gen/tuple (posint #(< % (max-arg)))
(posint #(< % (max-arg)))
(posint #(< % *max-limit*))))]]))
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;
(s/def ::zero+ nat-int?)
(s/def ::fizzbuzz-args
(s/with-gen
(s/and
(s/alt :_1-arg (s/cat :limit ::zero+)
:_3-arg (s/cat :n1 (s/and ::zero+ #(< 1 %))
:n2 ::zero+
:limit ::zero+))
(fn [[_ {:keys [n1 n2 limit]}]]
(if n1
(< n1 n2)
true)))
fizzbuzz-args-gen))
(defn n1-n2-limit-ret
"Destructures conformed fizzbuzz arguments and yields a uniform
[fizz-multiple, buzz-multiple, limit, return-value] vector"
[{:keys [args ret]}]
(let [arg-vals (second args)
limit (:limit arg-vals)]
(case (first args)
:_1-arg [3 5 limit ret]
:_3-arg [(:n1 arg-vals) (:n2 arg-vals) limit ret])))
;; A validator, uses multiples' checks and condition branching
(defn fizzbuzz-inout-valid?
"Verifies the relationship b/w conformed arguments and return value
of a fizzbuzz invocation"
[args&ret]
(let [[mult1 mult2 limit ret] (n1-n2-limit-ret args&ret)]
(and (= limit (count ret))
(->> ret
(map (fn [i [tag x]]
(cond
(= 0 (rem i mult1)(rem i mult2))
(= :fizzbuzz tag),
(zero? (rem i mult1))
(= :fizz tag),
(zero? (rem i mult2))
(= :buzz tag),
:else
(= i x)))
(range 1 (inc limit)))
(every? identity)))))
(s/def ::fizzbuzz-ret (s/* (s/alt :num ::zero+
:fizz (s/and string? #(= "Fizz" %))
:buzz (s/and string? #(= "Buzz" %))
:fizzbuzz (s/and string? #(= "FizzBuzz" %)))))
(s/fdef fizzbuzz
:args ::fizzbuzz-args
:ret ::fizzbuzz-ret
:fn fizzbuzz-inout-valid?)
(defn instrument []
(stest/instrument `fizzbuzz))
(defn exercise []
(s/exercise-fn `fizzbuzz))
(defn check
([num-tests]
(stest/check `fizzbuzz
;; somehow KLIPSE doesn't recognize stc/opts
{#?(:clj :clojure.spec.test.check/opts
:cljs :clojure.test.check/opts) {:num-tests num-tests}}))
([]
(check 10)))
;; (instrument)
;; (exercise)
;; OBSERVATIONS ON MY JVM / BROWSER
;; These succeed on and off, and rarely/never consistently with one another
;; (gen/sample (fizzbuzz-args-gen) 200)
;; (check 200)
;; (ncreasing the limit succeeds on the JVM
;; (binding [*max-limit* 500] (check 200))
;;;; WHY USE *max-limit* ? ;;;;;
;; Even though the arguments are expected to grow
;; gradually, it may not be gradual enough to run
;; more tests within reasonable time.
;;
;; For example, the following will take forever:
;; (binding [*max-limit* 100000] (check 2000))
;;
;; ...but the same number of tests with a smaller limit
;; takes a few seconds on the JVM:
;; (binding [*max-limit* 1000] (check 2000))
(println "Running 50 tests. This may take a few seconds...")
(let [results (check 50)]
(println "Done.")
results)
@viebel
Copy link

viebel commented Feb 9, 2017

@KingCode
Copy link
Author

KingCode commented Feb 9, 2017

Thank you Yehonathan, fantastic CR presentation! The current version runs fine on the JVM, but I get an error in Klipse because I am using JVM regex'es. Will change that for my next version..

@KingCode
Copy link
Author

KingCode commented Feb 10, 2017

Putting FizzBuzz at the start of the regex fixed the failing tests in KLIPSE/cljs (actually browser freezes, as opposed to failing before the change). **EDIT:**After much wrestling (including reader-conditionals) and writing custom generators, (check) runs fine up to about num-tests = 100 on my JVM and KLIPSE. Increasing the allowable input (*max-limit*) increases successful num-tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment