Fork me on GitHub
#clojure-spec
<
2017-07-20
>
jimmy06:07:14

hi guys, I'm in a situation that we have two kind of specs:

# "correct" spec, this one is used to do validation, instrument 
and # "test" spec, this one is mainly to use in gen test. Since when doing test, sometimes I want a function to handle all kind of input instead of the "correct" one generated by "correct" spec.
How do you guys manage these in terms of code organization ( "test" spec in test namespace, "correct" spec in the same ns of the function def ) or ... ? I would love to hear your idea on this.

seancorfield06:07:15

@nxqd I would have a single spec but override the generator.

seancorfield06:07:20

You want the same "correct" spec in both production code and test code -- but it's reasonable to have a generator that produces simpler data. You do not want the other way around.

seancorfield06:07:44

Perhaps you're not explaining what you're really trying to do?

seancorfield06:07:42

If you're explicitly doing validation inside the function, then the arguments it takes are a different spec, by definition, since the function takes a broader range of data -- it's expecting a broader range of data so that it can validate a small range of data and do something different and observable for the "invalid" data.

seancorfield06:07:08

i.e., (if (s/valid? ::some-spec input-data) (do-good-stuff input-data) (do-something-else input-data)) -- the function is anticipating input-data that does not conform to ::some-spec and it is expected, defined, testable behavior that the function does something else -- and the function accepts, as part of its core production spec input data that is beyond just ::some-spec. Does that make sense @nxqd ?

jimmy06:07:23

yeah you are correct. The approach I'm doing atm is basically the same as you mentioned above. what I have in mind is to design it like this:

code-ns
(s/fdef ... :args (s/cat :args ::args))
(defn ... )

test-ns
(s/fdef ... :args (s/cat :args ::args)) ;; the only different of this ::args is the gen function as you said.
;; register this fdef instead of the one above and run tests.
;; since the real implementation will still use the "correct" spec, there is no problem regarding valid? in function.
Is there any better solution to swap different gen fn in different env, so we don't have to create another test ns.

seancorfield06:07:26

No, what I'm saying is the function itself has one spec that is the same for test and production. It may be a different spec from what it checks (validates) inside the function.

seancorfield06:07:21

A generator for a spec cannot generate data that fails to conform to the spec, so the generator may generate a subset of acceptable data, but not a superset.

jimmy06:07:43

hmm, interesting. I got your point now

seancorfield06:07:16

Consider an API function that accepts arbitrary strings as arguments but then conforms the arguments to numeric values -- the function argument spec is basically string? and then it will have a separate spec for conforming numeric strings to numbers.

seancorfield06:07:25

Since you want the arguments to function to be mostly numeric, you need a custom generator that mostly generates numbers and calls str on them, but sometimes generates arbitrary strings (which will fail the other spec) and cause the function to do whatever error handling you expect.

jimmy06:07:29

agree. this clears my mind. What is your naming convention for fdef args spec and separate spec?

jimmy06:07:48

(s/def :fdef/arg )
and (s/def :correct/arg ) ?

seancorfield06:07:47

It depends on the business domain. In this case, I'd probably have a :domain/spec for the conforming spec and a :api/spec for the function argument spec.

jimmy06:07:06

great ! Thanks a lot. Have a nice day sir 😄

phreed17:07:07

Can I get some help with writing a spec? I want a vector of of alternating types, similar to a tuple spec but repeating where the types of the first and last are the same. The following is invalid but hopefully suggests what I am looking for. (s/def ::graph-path (s/tuple-ish ::node ::edge ::node ::edge ... ::node))

phreed18:07:04

In think I figured it out... (s/def ::graph-path (s/cat :h ::node :r (s/* (s/cat :e ::edge :n ::node)))