PurelyFunctional.tv Newsletter 329: Tool: cljdoc
Sign up for weekly Clojure tips, software design, and a Clojure coding challenge.
Clojure Tool 🔨
cljdoc is awesome. It can generate documentation for any Clojure project. Do try it on a test project you or someone else has created and published to Clojars. It generates beautiful documentation. Besides the documentation, the site has lots of helpful suggestions for how to make it generate better docs for your project. If you're a library author, it's invaluable.
You can watch a talk by the creator, Martin Klepsch, about how it works at IN/Clojure.
Clojure Media 🍿
:clojureD videos! You can watch them here (YouTube).
Clojure Puzzle 🤔
Last week's puzzle
The puzzle in Issue
was a redo of the previous
Rather than use example-based tests, the testbed uses test.check to generate random tests and check that they obey certain properties. What properties are included?
The first test is that all digits are included in the output number, and
no digit is left out. Basically, the number
maxcat returns has to use
all of the digits in its argument. This is a cheap test to run, even for
long lists of big integers.
The second test makes sure that the output is in fact a concatenation. Imagine if you could sort the digits instead of sorting the integers in the list. That would mean you could pull all the 9's to the left, which would make the final output bigger, but not really follow the spec. This one is slower, since you have to generate all possible permutations, so we can only run it on short lists.
The combination of the first two tests (one fast that works for long lists, one slow that works for short lists) (we hope) make sure that you are returning a concatenation. Using two tests like this is a common pattern in property-based testing.
The third and fourth tests also follow this pattern. The third test
generates lists of integers and checks that the return of
larger than all other concatenations. This test is slow, because we have
to generate all possible permutations. So we only run it for small lists
by setting a
max-size of 10. Unfortunately, the
max-size of 10 also
limits the size of the integers.
To cover larger integers, we need to run a very similar test, but
limited to lists of length 2. This is what the fourth test does. It
generates two integers that can be very large, so that they have many
digits. It then checks that the return of
maxcat is the largest.
With these last two property-based tests, we cover long lists with small integers and short lists with big integers. These cover everything except for long lists of big integers. Is this a problem? It seems unlikely, but I could be wrong.
Finally, there are a few example-based tests just to have some
well-known test cases. Val identified
(maxcat [1 10]) as a case that
many of the previous submissions didn't pass. This one is probably
covered by tests 3 & 4, but just to make sure, here it is.
(maxcat [Integer/MAX_VALUE Long/MAX_VALUE]) was suggested by Steve
because it catches implementations that don't use
instead rely on
read-string. Why not include
these? They don't take long to test and have been shown to exist in
I tested all of the previous submissions using the testbed. I copied the
ones that did pass to the new submission
I also modified a couple that were not passing simply due to using
read-string. I replaced
BigInteger. to make them
work, and they passed the tests. I also got a few new submissions.
Every submission in the gist has passed the test suite on my machine.
I'm going to continue doing this for puzzles in the future.
This week's challenge
email generator for test.check and clojure.spec
Email addresses are a common use for strings. And although a clojure.spec using a regex might be good for validation, it's not good for generating random emails addresses for property-based testing.
The challenge this week is to create a test.check generator for email addresses. This is both an implementation challenge and a design challenge. How completely does your generator cover the space of emails? How can it use the size parameter? Does the generator capture useful edge cases? What resources (for instance, documentation) can you use to make sure you are generating correct emails?
As usual, please send me your implementations. I'll share them all in next week's issue. If you send me one, but you don't want me to share it publicly, please let me know.