5

In using test.check I need a generator for strings of a certain length. Phone numbers, postal codes, social security numbers are all examples of this type of data. Although the examples appear to be only numbers, my question is for strings in general.

M Smith
  • 1,988
  • 15
  • 28

3 Answers3

9

Given length the generator below generates random strings:

(gen/fmap #(apply str %) 
          (gen/vector gen/char-alpha length))

(gen/vector gen/char-alpha length) generates sequences of characters and the fmap converts them into strings:

(apply str [\a \b]) ;; => "ab"

If a custom alphabet (say [\a \b \c]) is needed gen/char-alpha can be substituted with something like:

(gen/elements alphabet)

For more complex generators, like formatted phone numbers, test.chuck's string-from-regex might be a better choice than manually combining official generators.

muhuk
  • 15,777
  • 9
  • 59
  • 98
  • 1
    Thanks for pointing out test.chuck which has this and other good features. – M Smith Mar 14 '16 at 12:57
  • 2
    @MSmith I especially like `checking` (`testing` for generative) – muhuk Mar 14 '16 at 13:15
  • 1
    In case anyone else hits this with the latest versions of test.check, the correct version of this is now: `(gen/fmap #(apply str %) (gen/vector (gen/char-alpha) length)) `. Note the brackets around `gen/char-alpha`; without this you will see "Assert failed: First arg to vector must be a generator". – dmcgillen May 18 '18 at 17:55
2

This function will generate a string of a given length with characters from a given alphabet (optional). If you don't pass any alphabet as an argument, a default will be used, which you can of course change.

(defn generate-string 
  ([length]
   (generate-string length
            (map char (range 49 127))))
  ([length alphabet]
   (apply str (take length (repeatedly #(rand-nth alphabet))))))

Examples:

(generate-string 7 [\a \b \c])
"bacacbb"

(generate-string 10)
"mxqE<OKH3L"
MicSokoli
  • 841
  • 6
  • 11
2

You can use the more primitive generators to quickly build one that does just that:

For alphanumeric strings between min and max:

(sgen/fmap str/join (sgen/vector (sgen/char-alphanumeric) min max))

For alphanumeric strings of exactly a given length

(sgen/fmap str/join (sgen/vector (sgen/char-alphanumeric) length))

And you can modify (sgen/char-alphanumeric) accordingly to whatever your character range needs to be, such as a string of min/max with alphanumeric and underscore and dash character as well, with different frequencies of each character showing up:

(sgen/fmap str/join
                (sgen/vector
                 (sgen/frequency [[99 (sgen/char-alphanumeric)]
                                  [1 (sgen/elements #{"_" "-"})]])
                 min max))
Didier A.
  • 4,609
  • 2
  • 43
  • 45
  • Is there a way to specify both the spec and the generator for a fixed-length string simultaneously? E.g. I need to spec a 6-digit string of digits (0-9). – Petrus Theron Aug 17 '20 at 12:50
  • @PetrusTheron Not sure what you're asking. You can use s/with-gen to attach a custom generator to a spec, is that what you want? – Didier A. Aug 22 '20 at 09:36
  • This shouldn't have parenthesis around (sgen/char-alphanumeric). It should be `(sgen/fmap str/join (sgen/vector sgen/char-alphanumeric min max))` – triplej Aug 31 '21 at 16:43