1

Following on this question, and the blog post referenced there, is there a reason why prop/for-all does not just roll in this sort of capability directly? E.g. something like:

(require '[clojure.test.check.generators :as gen])
(require '[clojure.test.check.properties :as prop])
(require '[clojure.test.check.clojure-test :refer :all])

(defspec some-props-test
  (prop/for-all [n (gen/choose 1 10)
                 v (gen/vector gen/int n) ;; treat n like its produced value
                 e (gen/element v)]
    ... do stuff with n, v & e ...
  ))

Basically, I want to re-use the value produced by one generator in another generator and then reference both values produced within the actual test code. This would essentially extend the sugar/magic of for-all into allowing references to the generated values within the let-like block provided by the macro, as it works within the expression blocks below.

Please let me know if I'm missing something else that makes this possible or it just wouldn't make sense for some reason to implement.

Community
  • 1
  • 1
leeor
  • 17,041
  • 6
  • 34
  • 60

2 Answers2

6

I agree that this functionality would probably be more useful than what for-all currently does. The primary reason it hasn't been changed is for backwards-compatibility (though admittedly code using the old style wouldn't break, it would just not shrink as well as it used to).

But you have more options available than just monads:

  • gen/let, which uses let-style bindings (it isn't a drop-in replacement for for-all but you can use them together)
  • com.gfredericks.test.chuck.generators/for defined in the test.chuck helper library — it's like a fancier version of gen/let
  • com.gfredericks.test.chuck.properties/for-all, in the same library, which is a drop-in replacement for for-all
gfredericks
  • 1,312
  • 6
  • 11
  • great, `gen/let` replaces this nicely. I'll definitely check out `test.chuck` as well though. As a side note - I noticed the API docs link on the main `test.check` github page still points to `0.8.2`, the version before the introduction of `let`. – leeor Dec 29 '15 at 02:25
  • yeah, I have a TODO to update the API docs; last time I tried there was a confusing problem with the docs library, with no easy solution. Thanks for the reminder – it's useful to know it matters to somebody. – gfredericks Dec 29 '15 at 02:47
  • Np, thanks for all your hard work on the fantastic library! – leeor Dec 29 '15 at 03:12
  • tried out the `for-all` in `test.chuck` and it does exactly what I want. the way `test.check` `for-all` *should* work. Thanks again! – leeor Dec 29 '15 at 14:04
0

I found the later blog post in that series which fully flushes out usage with test.check (needed to read up a little on monads first to grok it). So first the monad can be declared:

(require '[clojure.algo.monads :as m])

(m/defmonad gen-m
    [m-bind gen/bind
     m-result gen/return])

(def vector-and-elem
  (m/domonad gen-m
    [n (gen/choose 1 10)
     v (gen/vector gen/int n)
     e (gen/element v)]
    [n v e]))

The gen-m monad allows referencing the value that will be generated for previously declared generators.

Then, it can be used directly in the for-all call:

(defspec some-props-test
  (prop/for-all [[n v e] vector-and-elem]
    ... do stuff with n, v & e ...
  ))

You can just pass all the values that are relevant to your constraint-checking code in the expression produced by the gen-m monad call via a vector (or map if you like) and de-structure it to get what you need.

Still, it would be nice if this was done automatically in for-all, but this works well enough.

leeor
  • 17,041
  • 6
  • 34
  • 60