I was playing around with spec2 and noticed that it doesn't allow for using local bindings in definitions
ie. this worked in spec.alpha:
(let [max-v 10]
(s/def ::foo (s/int-in 1 (inc max-v)))
(s/valid? ::foo 20))
but not in spec2, where it would throw an error "Unable to resolve symbol max-v"
Is this a deliberate design decision or was it ever officially supported in spec.alpha in the first place?
@qythium Spec 2 draws a much sharper line between symbolic specs and spec objects. Spec 2 has new facilities for creating specs programmatically that allow you to create such a spec, but in a different way.
You can read about the design decisions here https://github.com/clojure/spec-alpha2/wiki/Differences-from-spec.alpha @qythium
hmm.. that's slowly starting to make sense
So I would do this instead?
(let [max-v 10]
(s2/register ::foo
(s2/resolve-spec `(s2/int-in 1 (inc ~max-v))))
(s2/valid? ::foo 20))
Yup, that should work.
aha, so it's roughly like (s2/def kwd expr)
is macro sugar for (s2/register kwd (s2/resolve-spec
expr))`
Yes, if you look at the source https://github.com/clojure/spec-alpha2/blob/master/src/main/clojure/clojure/alpha/spec.clj you'll see def
calls register
and several macros use resolve-spec
to produce spec objects from symbolic forms.
I don't have any code handy but defop
is also handy for building new spec predicates.
That actually makes a lot more sense than the nested macros in the original Spec 🙂
which I recall macroexpanding to trace the logic and eventually just treated as magic
thanks!
If I have written a s/fdef
for a function, is there an easy way to generate property tests for that function (as part of a test suite), without having to manually (re)write all the generators?
Thanks @cjsauer - I am aware of test.check, but as I understand it, I will have to write my own generators again, rather than using fdef’s :args to check the function
Thanks @kenny - this looks like what I was imagining. Why do some not recommend it?
I think the argument is that they should run at a different time than regular unit tests because they are different. I don't have a problem with that -- run them in whatever way fits your company's workflow. These sort of tests are invaluable though. You should certainly run them before deployment. Having one test runner (https://github.com/lambdaisland/kaocha for us) is quite nice. We have a large codebase with thousands of tests. Running gen tests with our unit tests has not caused us any substantial pain. They typically take 1s or less to run. The more complex functions with custom gens can take longer. The results of a gen test do not fit well into the expected/actual framework clojure.test uses though. That is certainly another downside.
@ben606 re: generating args. I use the following code to generate args from an fdef:
(defn arg-gen
"Creates an argument generator for a given function symbol"
[sym]
(-> sym s/get-spec :args s/gen))
Like stest/check
but as part of my tests
Have you looked at https://clojure.github.io/spec.alpha/clojure.spec.test.alpha-api.html#clojure.spec.test.alpha/check-fn?
I saw this, but my understanding is that is runs the tests (like test.check/quick-check
) rather than creating a test (`deftest`). Have I misunderstood?
@ben606 maybe you’re looking for something like defspec
https://github.com/clojure/test.check/blob/master/doc/intro.md#clojuretest-integration
Although some don't recommend it, we do run spec's "check" in deftests using an "internal" (but public) library: https://github.com/Provisdom/test/blob/a61266ab281580af88fd394e9896cb85332cc5d7/src/provisdom/test/core.clj#L330 This will let you write something like this
(deftest my-fn-gen-test
(is (spec-check `user/my-fn)))
which will run & report st/check errors.