test-check

cheatex 2019-02-02T10:04:24.043700Z

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).

cheatex 2019-02-02T10:04:55.044300Z

Here is a generator for keys

(defn keys-from [map]
  (gen/elements (keys map)))

cheatex 2019-02-02T10:05:25.044700Z

and i can use it like this

(gen/sample (keys-from (gen/generate (gen/map gen/keyword gen/nat))))

cheatex 2019-02-02T10:06:18.045100Z

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)))

cheatex 2019-02-02T10:06:45.045600Z

But checking it fails, claiming map isn't a seq

cheatex 2019-02-02T10:12:08.046100Z

More precisely: https://gist.github.com/CheatEx/91659e49e5f6964aaf8a7a74529af287

cheatex 2019-02-02T10:14:50.047Z

This from (defspec ..), (tc/quick-check 1 prop-test) yields completely different failure.

cheatex 2019-02-02T10:27:11.047900Z

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

cheatex 2019-02-02T10:28:40.049300Z

So symbols in for-all bound to neither real values nor generators. I am totally confused. How do I write such property?

2019-02-02T15:04:44.050200Z

@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

2019-02-02T15:04:52.050500Z

(i.e., they're all done "in parallel")

2019-02-02T15:05:19.051100Z

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

2019-02-02T15:05:28.051300Z

so you're passing the map function to get

2019-02-02T15:06:05.051900Z

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

2019-02-02T15:06:48.052600Z

(def gen-map-and-key (gen/let [m (gen/map gen/keyword gen/nat), k (keys-from-gen m)] {:map m :key k}))

cheatex 2019-02-02T15:17:40.052900Z

Thanks for the explanation.

👍 1
cheatex 2019-02-02T15:19:48.055300Z

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.

2019-02-02T15:20:43.056300Z

For each trial, you mean?

cheatex 2019-02-02T15:20:46.056400Z

Is there a way to make few keys out of single generated map?

cheatex 2019-02-02T15:20:50.056600Z

Yes

cheatex 2019-02-02T15:21:50.057400Z

Probably, not sure of precise meaning of "trial" in test.check 🙂

cheatex 2019-02-02T15:23:23.059500Z

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.

2019-02-02T15:24:29.060200Z

you don't have to spell out the and, you could use every?

2019-02-02T15:24:33.060400Z

e.g., you could check every key

2019-02-02T15:24:49.061Z

and as long as the failing map entry is independent of the others, it should shrink to a singleton map

cheatex 2019-02-02T15:28:08.062200Z

They won't be independent in my case.

2019-02-02T15:28:43.062600Z

will it be somewhat independent? like should shrinking remove most of the entries?

2019-02-02T15:30:27.063Z

if that's a big problem, you could use the subsequence generator from this library https://github.com/gfredericks/test.chuck

2019-02-02T15:30:36.063300Z

to get a subset of the keys

2019-02-02T15:30:45.063700Z

and then that would shrink to a subset with only one failing key

cheatex 2019-02-02T15:36:33.064100Z

Looks like it could do the trick

cheatex 2019-02-02T15:40:31.066400Z

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?

2019-02-02T15:41:29.066700Z

I'm not sure what you mean by "and than than go to new one"

cheatex 2019-02-02T15:42:24.067300Z

I mean new value and derived generator

cheatex 2019-02-02T15:44:38.069100Z

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.

cheatex 2019-02-02T15:45:27.069900Z

e.g. number of calls to first generator << than number of calls to derived

2019-02-02T15:46:26.070300Z

there's a lot of ways to control the size or complexity of a generator

2019-02-02T15:46:35.070500Z

it might help to look at https://github.com/clojure/test.check/blob/master/doc/growth-and-shrinking.md

cheatex 2019-02-02T15:47:19.070900Z

I've seen it...

cheatex 2019-02-02T15:47:39.071400Z

Probably I need to work out better example and problem statement.

cheatex 2019-02-02T15:48:20.071900Z

Thank you anyway!

👍 1