Fork me on GitHub
#test-check
<
2019-02-02
>
cheatex10:02:24

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

cheatex10:02:55

Here is a generator for keys

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

cheatex10:02:25

and i can use it like this

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

cheatex10:02:18

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

cheatex10:02:45

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

cheatex10:02:50

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

cheatex10:02:11

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

cheatex10:02:40

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

gfredericks15:02:44

@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

gfredericks15:02:52

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

gfredericks15:02:19

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

gfredericks15:02:28

so you're passing the map function to get

gfredericks15:02:05

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

gfredericks15:02:48

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

cheatex15:02:40

Thanks for the explanation.

👍 5
cheatex15:02:48

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.

gfredericks15:02:43

For each trial, you mean?

cheatex15:02:46

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

cheatex15:02:50

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

cheatex15:02:23

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.

gfredericks15:02:29

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

gfredericks15:02:33

e.g., you could check every key

gfredericks15:02:49

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

cheatex15:02:08

They won't be independent in my case.

gfredericks15:02:43

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

gfredericks15:02:27

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

gfredericks15:02:36

to get a subset of the keys

gfredericks15:02:45

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

cheatex15:02:33

Looks like it could do the trick

cheatex15:02:31

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?

gfredericks15:02:29

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

cheatex15:02:24

I mean new value and derived generator

cheatex15:02:38

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.

cheatex15:02:27

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

gfredericks15:02:26

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

cheatex15:02:19

I've seen it...

cheatex15:02:39

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

cheatex15:02:20

Thank you anyway!

👍 5