Last active
February 19, 2017 13:37
-
-
Save KingCode/ef2ac029dbcf2881ad1762611f837d56 to your computer and use it in GitHub Desktop.
fizzbuzz example spec
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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) |
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..
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
That's amazing!
You can run your gist inside klispe: http://app.klipse.tech/?cljs_in.gist=KingCode/ef2ac029dbcf2881ad1762611f837d56&eval_only=1