clojure-spec

About: http://clojure.org/about/spec Guide: http://clojure.org/guides/spec API: https://clojure.github.io/spec.alpha/clojure.spec.alpha-api.html
Filipe Silva 2020-02-25T14:12:28.010Z

heya

Filipe Silva 2020-02-25T14:12:45.010500Z

is it possible to rename a key with s/keys ?

Filipe Silva 2020-02-25T14:13:11.010800Z

I'd like to do something like this

(s/def ::msg (s/and string? #(< 20 (count %))))
;; TODO: declaring a spec for a limited :db/id doesn't sound right. Figure out a better way.
(s/def :db/id (into #{} (range 1 10)))
(s/def ::map-datom (s/keys :req [:db/id ::msg]))
(s/def ::simple-tx (s/coll-of ::map-datom :kind vector? :min-count 0 :max-count 3))

Filipe Silva 2020-02-25T14:13:43.011400Z

but instead of (s/def :db/id, I'd like to do (s/def ::id

Filipe Silva 2020-02-25T14:14:18.012Z

and then on s/keys say that ::id is required but should show up as :db/id

Filipe Silva 2020-02-25T14:15:15.013Z

I'd like to do this because I don't want to be declaring the spec for the real :db/id, but would like to have an alternative version of it for testing

alexmiller 2020-02-25T14:27:56.013700Z

in short, no - the whole idea with spec is that attributes are primary and have meaning and maps are just aggregations of attributes

alexmiller 2020-02-25T14:28:57.014800Z

if you want a broader definition of ::db/id, then your spec should reflect that

Filipe Silva 2020-02-25T14:32:06.015200Z

ok, thank you for explaining it to me

Filipe Silva 2020-02-25T14:32:41.016Z

if I want to just generate test data that is a subset of all possible data, should I be using something else?

2020-02-25T14:34:35.017900Z

@filipematossilva: It sounds like you might be complecting generating data with specing. If in your tests you want to generate known id’s that are a subset of valid id’s, you can probably just pass a generator for that set where you need it.

Filipe Silva 2020-02-25T14:35:19.018200Z

yes, I think I am

Filipe Silva 2020-02-25T14:35:55.019Z

but s/keys does not support inline value specs (by design, according to the docs), so I don't think I can do that

Filipe Silva 2020-02-25T14:36:20.019800Z

(unless, of course, I am misunderstanding what you propose)

Filipe Silva 2020-02-25T14:36:22.019900Z

an alternative is to generate the test data with exercise, and then map over the results to change the key

Filipe Silva 2020-02-25T14:36:30.020300Z

which sounds fair enough, but I wanted to inquire first

alexmiller 2020-02-25T14:36:41.020600Z

you can pass generator overrides when doing most of the functions that generate in spec, but you still have to generate data that conforms to the spec

1👍
Filipe Silva 2020-02-25T14:39:04.022800Z

ah, then this was just something I did not know existed

Filipe Silva 2020-02-25T14:39:09.023100Z

is it https://clojuredocs.org/clojure.spec.alpha/gen?

Filipe Silva 2020-02-25T14:39:54.024100Z

or rather https://clojuredocs.org/clojure.spec.alpha/with-gen, as that seems to have examples

2020-02-25T14:40:31.024600Z

Totally! :-) When I first started with spec I made the mistake of over speccing to get spec to try and generate test data of the right shape… but now prefer the alternative of specing a little more broadly but using :gen ’s to generate the more precise data I want. Obviously it’s all trade offs. Anyway I was just suspecting Filipe might be falling into this same trap.

Filipe Silva 2020-02-25T14:42:44.025300Z

yes, I was 🙂

Filipe Silva 2020-02-25T14:45:04.025500Z

thank you

Filipe Silva 2020-02-25T14:45:18.026Z

I'll try to fiddle with the :gen option on s/keys and see where that takes me

2020-02-25T15:00:52.026400Z

:thumbsup: hope it helps

Filipe Silva 2020-02-25T15:11:11.027500Z

@rickmoynihan I'm trying to use the :gen key on s/keys, but I don't quite follow how I can use it to replace a single def

Filipe Silva 2020-02-25T15:11:30.028100Z

is that possible? or does :gen need to override how everything is generated

Filipe Silva 2020-02-25T15:11:53.028500Z

if you have an example of using it with s/keys it would be super useful

Filipe Silva 2020-02-25T15:23:15.028900Z

oh, I think this is the idea?

Filipe Silva 2020-02-25T15:23:20.029100Z

(s/def :db/id pos-int?)
(s/def ::small-pos-int? (into #{} (range 1 10)))
(s/def ::map-datom (s/keys :req [:db/id]))
(def small-pos-int-gen (s/gen ::map-datom {:db/id #(s/gen ::small-pos-int?)}))
(s/def ::small-id-map-datom (s/keys :req [:db/id]
                                    :gen (fn [] small-pos-int-gen)))

(s/exercise ::small-id-map-datom 10)
;; => ([{:db/id 5} {:db/id 5}] [{:db/id 3} {:db/id 3}] [{:db/id 1} {:db/id 1}] [{:db/id 1} {:db/id 1}] [{:db/id 7} {:db/id 7}] [{:db/id 8} {:db/id 8}] [{:db/id 6} {:db/id 6}] [{:db/id 8} {:db/id 8}] [{:db/id 6} {:db/id 6}] [{:db/id 2} {:db/id 2}])

Filipe Silva 2020-02-25T15:23:55.029800Z

making the real spec, then making another spec that has a generator that takes the real spec generator and adds an override

2020-02-25T15:26:20.030500Z

That’s not quite what I was suggesting…

2020-02-25T15:31:19.030700Z

(s/def ::foo integer?)

  (s/def ::foo-map (s/keys :req-un [::foo]))

 ;; The general generator (generates negative integers too)
  (s/exercise ::foo-map);; => ([{:foo -1} {:foo -1}] [{:foo 0} {:foo 0}] [{:foo 0} {:foo 0}] [{:foo 0} {:foo 0}] [{:foo 3} {:foo 3}] [{:foo -2} {:foo -2}] [{:foo -7} {:foo -7}] [{:foo -37} {:foo -37}] [{:foo -2} {:foo -2}] [{:foo -3} {:foo -3}])

;; An overriden tighter generator generating just 1 and 2 values for :football: 

  (s/exercise ::foo-map 10 {::foo-map (fn [] (s/gen #{{:foo 1} {:foo 2}}))});; => ([{:foo 1} {:foo 1}] [{:foo 2} {:foo 2}] [{:foo 1} {:foo 1}] [{:foo 2} {:foo 2}] [{:foo 2} {:foo 2}] [{:foo 1} {:foo 1}] [{:foo 1} {:foo 1}] [{:foo 2} {:foo 2}] [{:foo 2} {:foo 2}] [{:foo 2} {:foo 2}])

1❤️
2020-02-25T15:33:33.033Z

@filipematossilva: You can override on s/exercise, or st/check as well, so you can tailor your generation there rather than in the specs if you want.

Filipe Silva 2020-02-25T15:33:36.033200Z

ah on exercise itself

Filipe Silva 2020-02-25T15:33:38.033400Z

I see now

Filipe Silva 2020-02-25T15:33:47.033700Z

(s/def :db/id pos-int?)
(s/def ::small-pos-int? (into #{} (range 1 10)))
(s/exercise :db/id 10 {:db/id #(s/gen ::small-pos-int?)})

Filipe Silva 2020-02-25T15:34:00.033900Z

I understand now

Filipe Silva 2020-02-25T15:34:08.034300Z

thank you for taking the time to make the examples for me

2020-02-25T15:34:13.034600Z

:thumbsup:

Filipe Silva 2020-02-25T15:34:16.034800Z

it was very helpful

2020-02-25T15:36:50.036900Z

essentially s/exercise / st/check are the testing entry points to spec; so you can split the concerns of specing and overriding generation/testing at those entry points… whilst s/conform s/valid? and s/explain-xxx are the checking side of spec — and consequently don’t support generators at all (because they’re a different concern)

2020-02-25T15:36:57.037100Z

at least that’s how I think of it

2020-02-25T15:37:12.037400Z

There are probably more nuanced explanations 🙂

Filipe Silva 2020-02-25T15:39:08.037900Z

that sounds much more sensible than I what was attempting

2020-02-25T15:39:53.038400Z

its ok been there, done it, got burned! 🙂