Fork me on GitHub
#clojure-spec
<
2017-08-25
>
thedavidmeister05:08:25

when i use spec/exercise for things like string? and pos-int? i mostly get blank or 1 character strings and numbers 1 and 2

thedavidmeister05:08:31

is there a way to get it to "try harder"

thedavidmeister05:08:16

and give me longer strings more often, with more variety, like emojis and non english alphanumeric characteres

thedavidmeister05:08:38

and give me random ints anywhere in the full range of available positive integers?

thedavidmeister05:08:13

i really like the idea of using spec to help me generate tests, but it doesn't give me a lot of confidence to know that most of my tests are just passing "" and 1

thedavidmeister05:08:27

i just realised that if i (spec/exercise string? 1) i almost always get ""

thedavidmeister05:08:44

but if i (spec/exercise string? 100) then the later strings are much longer

thedavidmeister05:08:20

so i could do something like (ffirst (shuffle (spec/exercise pos-int? 100))) but this seems inefficient...

seancorfield05:08:14

@thedavidmeister I'm a bit puzzled about how you're running tests here?

seancorfield05:08:57

Are you using spec.test's check on functions? Or test.check with properties etc?

thedavidmeister05:08:37

uh, nothing like that atm

thedavidmeister05:08:50

i've just been working on creating specs for my existing codebase

thedavidmeister05:08:02

and i've got existing tests that i've handrolled examples for

thedavidmeister05:08:17

but i thought i could use spec/exercise as a "drop in" replacement for my examples

seancorfield05:08:54

We have a couple of places where we do (rand-nth (map first (s/exercise ::my-spec 100))) which is an equivalent to what you have so it's a reasonable approach for getting a specific random piece of test data.

thedavidmeister05:08:07

just checking that i'm not crazy 🙂

seancorfield05:08:08

I suspect rand-nth is going to be faster than first of shuffle (and (first (rand-nth ...))` is probably the fastest).

thedavidmeister05:08:18

is there a way to get it to be lazy?

seancorfield05:08:47

Lazy how? You only want one value and you want a random one out of 100...

thedavidmeister05:08:09

yes but it's clearly doing something different for the 100th to the 1st item that it generates

thedavidmeister05:08:49

i want the logic of the 100th iteration

thedavidmeister05:08:57

but not the 99 values that come before it

seancorfield05:08:27

Since we're talking about tests I wouldn't be too concerned about efficiency.

thedavidmeister05:08:40

it just seems so wasteful

thedavidmeister05:08:54

i'm not normally "that guy" but...

seancorfield06:08:19

FWIW, (first (rand-nth (s/exercise string? 100))) takes between 6 and 30ms on my low-powered laptop

thedavidmeister06:08:38

well the other half of my q

thedavidmeister06:08:49

is there a way to get string? to be less alphanumeric-y?

seancorfield06:08:05

What do you want it to be?

thedavidmeister06:08:14

i sort of expected utf-8

thedavidmeister06:08:28

so :poop: emojis and japanese kanji, etc.

seancorfield06:08:58

Some of the built-in generators are a bit conservative. Take a look at test.chuck and its regex generator.

seancorfield06:08:53

We have a crazy email address regex and use test.chuck to produce random email addresses and it generates absolutely wild UTF-8 strings 🙂

thedavidmeister06:08:49

oh yeah string-from-regex looks cool

seancorfield06:08:06

We also have a wild password spec (with complex rules about UTF-8 character classes and length etc), but for testing we override to use a much simpler ASCII regex generator.

thedavidmeister06:08:07

so how do i plug that into spec?

seancorfield06:08:18

We're testing different things there.

seancorfield06:08:43

s/exercise lets you specify overrides, or you can use s/with-gen to provide a custom generator for any spec.

thedavidmeister06:08:28

ok cool, so can i do like (s/with-gen string? (partial string-from-regex #".*")

thedavidmeister06:08:31

or is that not right?

seancorfield06:08:55

It needs to be a zero arity function that returns a generator.

gklijs06:08:16

like: (s/def :nl.klijs.person/first-name (let [re #“^[A-Z]{1}[a-z]{2,20}“] (s/spec string? :gen #(sg/string-generator re))))

seancorfield06:08:19

an example from our code

(s/def ::date-of-birth (s/with-gen age-18-120?
                         (fn [] (s/gen (s/inst-in (wdt/years-ago 120)
                                                  (wdt/years-ago 18))))))

seancorfield06:08:03

We wrap test.chuck so we don't drag it in for production code but it can come in during testing so we have

(defn fn-string-from-regex
  "Return a function that produces a generator for the given
  regular expression string."
  [regex]
  (fn []
    (require '[com.gfredericks.test.chuck.generators :as xgen])
    (let [string-from-regex (resolve 'xgen/string-from-regex)]
      (string-from-regex regex))))

seancorfield06:08:34

and then

(s/def ::password (s/with-gen (s/and string? password-valid?)
                    (wgen/fn-string-from-regex #"[a-zA-Z0-9 _!@#$%\^&*\(\)\).]{8,}]")))

seancorfield06:08:49

(`password-valid?` is a gnarly predicate)

thedavidmeister06:08:35

and then once you've done that, exercise will know what to do with ::password

seancorfield06:08:06

Yup, it will generate based on that regex.

thedavidmeister06:08:34

that sounds much more thorough than alphanumeric strings

seancorfield06:08:36

Which will satisfy our rules (`password-valid?`) but much more easily -- and still readable.

thedavidmeister06:08:04

soz, i have another question while i go through all this

thedavidmeister06:08:14

is there a predicate for date/times?

gklijs06:08:21

I needed to use it to be able to even generate maps containing an email

thedavidmeister06:08:41

@gklijs yeah i'm running into limitations with the default string? generator pretty fast too

seancorfield06:08:29

@thedavidmeister We have a set of specs and generators for date/times but it's a fair bit of code because we actually need to coerce from strings to dates, and we want to generate formatted dates.

seancorfield06:08:00

But for basic stuff you can just use inst?

thedavidmeister06:08:02

oh yeah, i'm slowly working through moving between js dates, goog dates, joda dates and iso8601 strings

thedavidmeister06:08:13

just wanted something basic for now though 🙂

thedavidmeister06:08:29

ah inst? looks great

thedavidmeister06:08:59

it seems to strongly prefer 1970-01-01

thedavidmeister06:08:49

even if i do (first (rand-nth (spec/exercise inst? 1000))) it rarely moves away from 1970

seancorfield06:08:14

Yeah, the inst? generator is pretty conservative.

thedavidmeister06:08:38

know of anything like that regex lib?

thedavidmeister06:08:09

i feel like even using a random int as a timestamp would be better 😕

gklijs06:08:08

maybe with dates you want one based on the current date and add/substract some random year/month/seconds etc?

seancorfield06:08:31

Hence our age range spec / generator 🙂

thedavidmeister06:08:13

i think i'm going to go for a jog and think about how i can adapt to my stuff 🙂

seancorfield06:08:05

One thing I've found important with clojure.spec is to be careful not to over-specify things and to override generators to produce more "sane" data (for your problem domain) -- generating just a small subset of possible conforming values sometimes.

thedavidmeister06:08:18

i think that will be ok for me atm

thedavidmeister06:08:29

as i'm replacing hard-coded values in tests anyway

thedavidmeister06:08:42

it will be an incremental process

thedavidmeister06:08:52

as i gradually introduce more complexity of what is specced

thegeez09:08:51

@thedavidmeister spec generator have the notion of a size parameter (100 here). However, this is not a minimum length: (clojure.test.check.generators/generate (clojure.spec.alpha/gen string?) 100) and clojure.test.check.generators/resize

martinklepsch15:08:19

how do you express that all keys of a map should be keywords? I want to spec a structure that looks like this:

{vec {keyword {string {:some-known-key ::x
                         :another-known-key ::y}}}}

seancorfield15:08:00

(map-of keyword? ::value-spec)

martinklepsch15:08:45

@seancorfield thanks, I’ll look into map-of

ikitommi18:08:57

@martinklepsch map-of is the right answer, but your pseudo looks much like the thing that the data-specs are eating 😉 (and generating map-ofs & friends)

(require '[clojure.spec.alpha :as s])
(require '[spec-tools.data-spec :as ds])

(s/def ::x string?)
(s/def ::y int?)

(let [martin {vector? {keyword? {string? {:some-known-key ::x
                                          :another-known-key ::y}}}}
      value {[1 2 3] {:a {"b" {:some-known-key "nice"
                               :another-known-key 18}}}}
      spec (ds/spec ::martin martin)]
  (s/valid? spec value))
;; true

martinklepsch18:08:30

@ikitommi ah that indeed seems like a nice utility!

eraserhd19:08:29

So I just discovered that (s/def :foo/bar :baz/quux) doesn't work if :baz/quux hasn't already been defined. Is this new?

eraserhd19:08:48

Is it just s/keys that defers lookup?

eraserhd19:08:02

And if so, does that mean that I can't redefine :baz/quux later?

Alex Miller (Clojure team)19:08:07

almost everything defers lookup. this is a known (and unresolved) issue.

eraserhd19:08:11

If I wrap it with s/spec, will it defer?

eraserhd19:08:46

apparently... more testing in half an hour or so