27 March 2015
After spending some time working with generating random strings for testing regular expressions, I decided to write a utility to make things easier to read and reduce the amount of code in my tests. The library is here: https://github.com/stephenkhess/RandomStringBuilder I'll revisit the original example of formatting Canadian postal codes, so here's the class under test:
class CanadianPostalCodeFormatter {
def format(input) {
// regex that captures the forward sortation area (the first part),
// the optional local delivery unit (the second part), and ignores the delimiter
def matcher = input =~ /(?i)^([A-Z][0-9][A-Z])(?:[ -]?([0-9][A-Z][0-9]))?$/
if (matcher.size() > 0) {
// return the concatenated-by-space non-null matched groups
matcher[0][1..2].findAll{ it }*.toUpperCase().join(" ")
}
}
}
Using RandomStringBuilder, our test of formatting extended Canadian postal codes would look like:
class CanadianPostalCodeFormatterSpec extends Specification {
def "valid input should return space-delimited uppercased forward sortation area and local delivery unit"() {
given:
def formatter = new CanadianPostalCodeFormatter()
and: "a forward sortation area, like 'k1A'"
def forwardSortationArea = new RandomStringBuilder().
oneLetter().oneNumber().oneLetter().
build()
and: "a local delivery unit, like '0B1'"
def localDeliveryUnit = new RandomStringBuilder().
oneNumber().oneLetter().oneNumber().
build()
and: "the concatenated input, like 'k1A-0B1'"
def input = new RandomStringBuilder().
is(forwardSortationArea).
optionalCharacterOf(" -").
is(localDeliveryUnit).
build()
when:
def output = formatter.format(input)
then:
output == "${forwardSortationArea.toUpperCase()} ${localDeliveryUnit.toUpperCase()}"
where:
i << (1..100)
}
}
In this particular case, the test is run 100 times which should thoroughly exercise the letter and number ranges.
A more complicated regular expression is one that matches UK postal codes:
^([A-Z]{1,2}[0-9][0-9A-Z]?) ?([0-9][A-Z]{2})$
RandomStringBuilder makes short work of generating test inputs and in a very readable format:
def outwardCode = new RandomStringGenerator.Builder().
atLeastOneLetter(2). // 1 or 2 letters
oneNumber().
optionalAlphaNumeric().
build()
def inwardCode = new RandomStringGenerator.Builder().
oneNumber().
nLetters(2).
build()
def input = new RandomStringGenerator.Builder().
is(outwardCode).
optionalSpace().
is(inwardCode).
build()