Skip to content

Instantly share code, notes, and snippets.

@bendisposto
Created March 16, 2017 09:21
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bendisposto/c3456187a018d420262eb3e90035f38b to your computer and use it in GitHub Desktop.
Save bendisposto/c3456187a018d420262eb3e90035f38b to your computer and use it in GitHub Desktop.
REPL Session - Generative Testing, Softwerkskammer Ruhr, 15.3.2017
(ns bsp.core
(:require [clojure.repl :refer :all]
[clojure.spec :as spec]
[clojure.test.check :refer [quick-check]]
[clojure.test.check.generators :as gen]
[clojure.test.check.properties :refer [for-all]]
[clojure.test :refer [is are run-tests deftest]]))
(defn ultrasort [coll] (seq (into (sorted-set) coll)))
(set! *print-length* 20)
;; Clojure is a dialect of the Lisp programming language [...]
;; Clojure's focus on programming with immutable values and explicit
;; progression-of-time constructs are intended to facilitate the
;; development of more robust programs, particularly multithreaded
;; ones. (Wikipedia)
(comment
;; There are numbers
3
1.99
2/6
;; There are booleans
true
false
;; There are Strings
"Hallo Softwerkskammer"
;; Multiline Strings!
"Hallo
Softwerkskammer"
;; There are Keywords (Ruby: Symbols, Java: kinda Enums)
:foo
;; And we can have namespaced keywords (good names are rare)
:trololo/tralala
::lol
;; There is nil (Java: null)
nil
;; There are Sequences
[1 2 3] ;; Look Ma, no comma!
[1,2,,,3] ;; I couldn't care less
;; There are Maps
{1 2 3 4}
;; Everything can have a different type
{:key :value 3 "drei"}
;; Really ... any type
{:a :b, 4 {}, [[] 6] "doh"}
;; And finally: THE MIGHTY PARENS
(println "Hallo Softwerkskammer")
(+ 2 4 5)
;; First argument is the Funktion, all other argument are parameters
;; Compare: square(4) vs. (square 4)
;; Did I mention that Clojure runs on the JVM?
(type "Hallo")
(type 4)
(.toLowerCase "ROFL")
;; Now let's write some functions
(def square (fn [x] (* x x)))
(square 3)
;; (def ... (fn ...)) is very popular
(defn cube [x] (* x x x))
(cube 3)
;; Clojure is a functional language
;; Functions are first class
(map cube (range 10))
(->> (range 10)
(map cube))
(filter even? (range 20))
(defn make-adder [x]
(fn [y] (+ x y)))
(def add17 (make-adder 17))
(add17 4)
;; Do you want to learn more?
;; Visit our Dojo, next Thursday (23.3.2017)
;; in Düsseldorf: https://goo.gl/Z1bcoI
;; You've just learned enough Clojure to participate
;; Now let us do some testing
;; @edial:
;; QA Engineer walks into a bar.
;; Orders a beer.
;; Orders 0 beers.
;; Orders 999999999 beers.
;; Orders a lizard.
;; Orders -1 beers.
;; Orders a sfdeljkn
(deftest squaring-3
(is (= 10 (square 3)) "3 squared should be 9"))
(deftest ultrasort-simple-cases
(are [x y] (= x y)
(ultrasort [1 2]) [1 2]
(ultrasort [3 1 2]) [1 2 3]
(ultrasort [1 3 2]) (ultrasort [3 2 1])))
(run-tests)
;; ok ok, now generative testing ...
;; what are we doing, when we write tests?
;; this is a generator
gen/int
;; sample takes a generator and asks for some generated values
(gen/sample gen/boolean)
(gen/sample gen/int 20)
;; There are predefined generators for all standard-types
(gen/sample gen/nat 15)
(gen/sample gen/string-alphanumeric)
(gen/sample gen/string)
(gen/sample gen/keyword)
;; Just give me some valid Clojure data
(last (gen/sample gen/any 50))
;; a generator to randomly select from a collection
(gen/sample (gen/elements [1 "zwei" [3.0] :vier]))
;; a very boring generator
(gen/sample (gen/return :gähn))
;; turns a value into a generator that produces that value
;; we also have generators for collections
(gen/sample (gen/vector gen/int))
(gen/sample (gen/map gen/int gen/string-ascii))
;; a generator for tuples
(gen/sample (gen/tuple gen/int gen/boolean gen/nat))
;; a generator that randomly selects from other generators
(gen/sample (gen/one-of [gen/int gen/boolean gen/string-alpha-numeric]) 20)
;; This is very different from elements!
;; a filter
(gen/sample (gen/such-that even? gen/int))
;; a filter that removes empty results
(def sort-input (gen/not-empty (gen/vector gen/int)))
(gen/sample sort-input)
;; transformation
(gen/sample (gen/fmap square gen/int))
;; this is not the same as (map square (gen/sample gen/int))
;; Guess how long it will take to produce Strings that start with https://
(gen/sample (gen/such-that (fn [s] (.startsWith s "https://")) gen/string))
(gen/sample (gen/fmap (partial str "https://") gen/string-alphanumeric))
(+ 6 3 3 3)
(def foo (partial + 17))
(foo 4 5)
;; complex combination
;; need a vector of some type and a single number taken from the vector
(def vector-gen (gen/not-empty (gen/vector gen/any)))
(gen/sample vector-gen)
(defn vector-element-tuple-gen [v]
(gen/tuple (gen/return v)
(gen/elements v)))
(gen/sample (vector-element-tuple-gen [1 2 3 4 5]))
(gen/sample (gen/bind vector-gen vector-element-tuple-gen))
;; For the advanced functional programmer: the names bind and return
;; are not chosen by accident
;; Specifying properties:
;; (for-all [v1 GENERATOR1 v2 GENERATOR2 ...] (PREDICATE v1 v2 ...))
;; Let's test ultrasort, the blazing fast sort for nonempty vectors
(def nonempty-gen (gen/not-empty (gen/vector gen/int)))
(defn sorted? [v] (apply <= v))
(apply + 1 2 [3 4 5 56])
(+ 1 2 3 4 5 56)
(sorted? [1 2 3 4])
(sorted? [1 7 3 4])
(def sorted-prop
(for-all [v nonempty-gen]
(sorted? (ultrasort v))))
(quick-check 100 sorted-prop)
(quick-check 10000 sorted-prop :seed 1472739084232)
;; Ok, does ultrasort work?
(def equivalence
(for-all [v nonempty-gen]
(let [v' (ultrasort v)]
(= v' (sort v)))))
(def r (quick-check 100 equivalence :seed 1489425237298))
(def ce (first (:fail r)))
ce
(get-in r [:shrunk :smallest])
;; sorted? is not sufficient
;; this satisfies the sorted? property:
(defn veryquicksort [v] [0])
;; Sorting is finding a permutation of the elements such that sorted? is true
(def permutation
(for-all [v nonempty-gen]
(= (frequencies v)
(frequencies (ultrasort v)))))
(frequencies [1 2 3 2 1 2 2 2])
(quick-check 100 permutation)
(defn ultrasort ;; actually this is quicksort from http://eddmann.com/posts/quicksort-in-clojure/
[[pivot & coll]]
(when pivot
(concat (ultrasort (filter #(< % pivot) coll))
[pivot]
(ultrasort (filter #(>= % pivot) coll)))))
(quick-check 1000 permutation)
(quick-check 1000 sorted-prop)
)
(defproject bsp "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.9.0-alpha14"]
[org.clojure/test.check "0.9.0"]]
:profiles {:dev {:dependencies [[pjstadig/humane-test-output "0.8.1"]]
:injections [(require 'pjstadig.humane-test-output)
(pjstadig.humane-test-output/activate!)]}})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment