Why not automate the manual work involved in writing individual test cases? Why not make them more robust by applying a much wider coverage of the input domain compared to picking edge cases by hand? Why not separate input domain definition from validation? Generative testing addresses these problems by testing random_(ish)_ly generated inputs against the expected properties of output instead of expected values. Traditional unit tests are usually written by hand-picking cleverly chosen edge cases and testing them, encapsulating input and validation in (typically) single functions/methods.
An example of traditional unit tests in Clojure:
(defn add [x y] (+ x y))
(is (=5 (add23))))
Generative testing, on the other hand separates the definition of the input domain (possible inputs for the function being tested) with the concept of generators and validators.
When you have your generators set up, it’s time to declare the properties which is a way to formally define the test:
(prop/for-all [v (gen/vector gen/int)]
(= (sort v) (sort (sort v)))))
In the above example we’re stating that for all random vectors of integers (this is what (gen/vector gen/int), our generator gives us) sorting once should result in the same vector as sorting twice. We can quickly check if the test fails or not: