Hi. I try to write a test and stuck with generator/bind semantics. Say I want to write a test map's get (my actual goal is more complex, this one just a good showcase).
Here is a generator for keys
(defn keys-from [map]
(gen/elements (keys map)))
and i can use it like this
(gen/sample (keys-from (gen/generate (gen/map gen/keyword gen/nat))))
Now I can write a property
(def prop-test
(prop/for-all [map (gen/map gen/keyword gen/nat)
key (keys-from map)]
(get map key)))
But checking it fails, claiming map
isn't a seq
More precisely: https://gist.github.com/CheatEx/91659e49e5f6964aaf8a7a74529af287
This from (defspec ..), (tc/quick-check 1 prop-test) yields completely different failure.
My another attempt
(defn keys-from-gen [map-gen]
(gen/let [map (gen/such-that (complement empty?) map-gen)]
(gen/elements (keys map))))
(gen/sample (keys-from-gen (gen/map gen/keyword gen/nat))) ; works well
(def prop-test-gen
(prop/for-all [map (gen/map gen/keyword gen/nat)
key (keys-from-gen map)]
(get map key)))
(defspec map-gen-test 2 prop-test-gen)
fails with
Uncaught Error: Assert failed: Second arg to such-that must be a generator
So symbols in for-all
bound to neither real values nor generators. I am totally confused. How do I write such property?
@cheatex the confusion comes from the fact that the binding names in prop/for-all
aren't visible at all to the expressions for subsequent bindings
(i.e., they're all done "in parallel")
normally you'd get whatever error you normally get when you use a name that's not visible, but in this case you're getting more confusing errors because you're happening to use the name map
, which already means cljs.core/map
so you're passing the map
function to get
prop/for-all
doesn't have a way to get the bind
semantics, so you have to do this with an additional generator to help you compose things
(def gen-map-and-key (gen/let [m (gen/map gen/keyword gen/nat), k (keys-from-gen m)] {:map m :key k}))
Thanks for the explanation.
I've made this kind of generator for my problem, but i'm not perfectly happy with it. It basically needs to generate a huge data structure to create a key for it... and than throw it away and create a new one just to make another key.
For each trial, you mean?
Is there a way to make few keys out of single generated map?
Yes
Probably, not sure of precise meaning of "trial" in test.check 🙂
I've had idea of generating [map [&keys]] but that would make properties to be a huge and
and hard to find exact failing key.
you don't have to spell out the and
, you could use every?
e.g., you could check every key
and as long as the failing map entry is independent of the others, it should shrink to a singleton map
They won't be independent in my case.
will it be somewhat independent? like should shrinking remove most of the entries?
if that's a big problem, you could use the subsequence
generator from this library https://github.com/gfredericks/test.chuck
to get a subset of the keys
and then that would shrink to a subset with only one failing key
Looks like it could do the trick
But generally. Is it impossible to use some generated value to set up other generator, use both value and generator in a property and than go to new one?
I'm not sure what you mean by "and than than go to new one"
I mean new value and derived generator
So in my example when I run property check with size 50 it (just sample numbers to show magnitudes) generates 5 maps and 10 keys for every map.
e.g. number of calls to first generator << than number of calls to derived
there's a lot of ways to control the size or complexity of a generator
it might help to look at https://github.com/clojure/test.check/blob/master/doc/growth-and-shrinking.md
I've seen it...
Probably I need to work out better example and problem statement.
Thank you anyway!