Skip to content

Instantly share code, notes, and snippets.

@ericnormand
Last active July 29, 2019 20:39
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 ericnormand/82471e9341a697be700241c07038758d to your computer and use it in GitHub Desktop.
Save ericnormand/82471e9341a697be700241c07038758d to your computer and use it in GitHub Desktop.

property-based tests of quartiles

Now that we have implementations of quartiles, how do we know it works? What properties would you want the quartiles values to obey?

Here are a few properties to implement:

  1. The median (q2) should be between the smallest and largest numbers.
  2. q0 <= q1 <= q2 <= q3 <= q4

These are invariants that we can guarantee. But invariants are just one way to create properties. We can also do a metamorphic test:

  1. Generate a random list l1.
  2. Calculate the quartiles l1q.
  3. Add a number to the list l1 that is greater than the max, call it l2.
  4. Calculate the quartiles l2q.

The values in l2q should be greater than or equal to the corresponding values in l1q.

Implement these three properties. That's the challenge this week.

(tc/quick-check 100
(prop/for-all [nums (gen/not-empty (gen/vector gen/large-integer))]
(let [qs (quartiles nums)
{:keys [q2]} qs]
(<= (apply min nums)
q2
(apply max nums)))))
(tc/quick-check 100
(prop/for-all [nums (gen/not-empty (gen/vector gen/large-integer))]
(let [qs (quartiles nums)
{:keys [q0 q1 q2 q3 q4]} qs]
(<= q0 q1 q2 q3 q4))))
(tc/quick-check 100
(prop/for-all [num1s (gen/not-empty (gen/vector gen/large-integer))
diff gen/nat]
(let [q1s (quartiles num1s)
num2s (conj num1s (+ diff (apply max num1s)))
q2s (quartiles num2s)]
(every? (fn [k]
(<= (get q1s k) (get q2s k)))
(keys q1s)))))
(ns scratchpad.pftv.336
(:require [clojure.test :refer [deftest is testing run-tests]]
[scratchpad.pftv.335 :refer [median quartiles]]
[clojure.test.check :as tc]
[clojure.test.check.generators :as gen]
[clojure.test.check.properties :as prop]
[clojure.test.check.clojure-test :refer [defspec]]))
;; The median (q2) should be between the first and last numbers.
(def prop-median-between-first-and-last
(prop/for-all [v (gen/not-empty (gen/vector gen/small-integer))]
(let [[q0 q1 q2 q3 q4] (quartiles v)]
(<= (apply min v) q2 (apply max v)))))
(defspec median-between-first-and-last
100 ;; number of iterations
prop-median-between-first-and-last)
;; q0 <= q1 <= q2 <= q3 <= q4
(def prop-quartiles-are-sorted
;; need a vec with at least size 2
(prop/for-all [v (gen/vector gen/small-integer 2 25)]
(let [q (quartiles v)]
(apply <= q)))) ;; or (= q (sort q))
(defspec quartiles-are-sorted
100
prop-quartiles-are-sorted)
;; Metamorphic test:
;;
;; These are invariants that we can guarantee. But invariants
;; are just one way to create properties. We can also do a metamorphic test:
;;
;; 1. Generate a random list l1.
;; 2. Calculate the quartiles l1q.
;; 3. Add a number to the list l1 that is greater than the max, call it l2.
;; 4. Calculate the quartiles l2q.
;;
;; The values in l2q should be greater than or equal to the corresponding values
;; in l1q.
(def prop-metamorphic-test
(prop/for-all [l1 (gen/vector gen/small-integer 2 25)
a (gen/fmap inc gen/nat)]
(let [b (+ a (apply max l1)) ;; make sure b is larger than q4 of l1
l2 (conj l1 b)
l1q (quartiles l1)
l2q (quartiles l2)]
(every? identity (map >= l2q l1q)))))
(defspec metamorphic-test
100
prop-metamorphic-test)
;; for debugging problems
(comment
(tc/quick-check 100 prop-median-between-first-and-last)
(tc/quick-check 100 prop-quartiles-are-sorted)
(tc/quick-check 100 prop-metamorphic-test))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment