Day 7 – Test All The Things

Perl 6, like its big sister Perl 5, has a long tradition of testing. When you install any Perl module, the installer normally runs that module’s test suite. And of course, as a budding Perl 6 module author, you’ll want to create your own test suite. Or maybe you’ll be daring and create your test suite before creating your module. This actually has several benefits, chief among them being your very first user, even before it’s written.

Before getting to actual code, though, I’d like to mention two shell aliases that I use very frequently –

alias 6='perl6 -Ilib'
alias 6p="prove -e'perl6 -Ilib'"

These aliases let me run a test file quickly without having to go to the trouble of installing my code. If I’m in a project directory, I can just run

$ 6 t/01-core.t
ok 1 - call with number
ok 2 - call with text
ok 3 - call with formatted string
1..3

and it’ll tell me what tests I’ve run and whether they all passed or not. Perl 6, just like its big sister Perl 5, uses the ‘t/’ directory for test files, and by convention the suffix ‘.t’ to distinguish test files from packages or scripts. It’s also got a built-in unit testing module, which we used above. If we were testing the sprintf() internal, it might look something like

use Test;

ok sprintf(1), 'call with number';
ok sprintf("text"), 'call with text';
ok sprintf("%d",1), 'call with formatted string';

done-testing;

The ok and done-testing functions are exported for us automatically. I’m using canonical Perl 6 style here, not relying too much on parentheses. In this case I do need to use parentheses to make sure sprintf() doesn’t “think” ’empty call’ is its argument.

ok takes just two arguments, the truthiness of what you want to test, and an optional message. If the first argument is anything that evaluates to True, the test passes. Otherwise… you know. The message is just text that describes the test. It’s purely optional, but it can be handy when the test fails as you can search for that string in your test file and quickly track down the problem. If you’re like the author, though, the line number is more valuable, so when you see

not ok 1 - call with number
# Failed test 'call with number'
# at test.t line 4
ok 2 - call with text
ok 3 - call with formatted string
1..3

in your test, you can immediately jump to line 4 of the test file and start editing to find out where the problem is. This gets more useful as your test files grow larger and larger, such as my tests for the Common Lisp version of (format) that I’m writing, with 200+ tests per test file and growing.

Finally, done-testing simply tells the Test module that we’re done testing, there are no more tests to come. This is handy when you’re just starting out and you’re constantly experimenting with your API, adding and updating tests. There’s no test counter to update each time or any other mechanics to keep track of.

It’s optional, of course, but other tools may use the ‘1..3’ at the end to prove that your test actually ran to completion. The tool prove is one, Jenkins unit testing and other systems may need that as well.

It depends…

on what your definition of ‘is’ is. While the ok test is fine if you’re only concerned with the truthiness of something, sometimes you need to dig a little deeper. Perl 6, just like its big sister, can help you there.

is 1 + 1, 2, 'prop. 54.43, Principia Mathematica';

doesn’t just check the truthiness of your test, it checks its value. While you could easily write this as

ok 1 + 1 == 2, 'prop. 54.43, Principia Mathematica';

using is makes your intent clear that you’re focusing on whether the expression 1+1 is equal to 2; with the ok version of the same statement, it’s unclear whether you’re testing the ‘1 + 1’ portion or the ‘==’ operator.

These two tests alone cover probably a good 80% of your testing needs, is handles basic lists and hashes with relative aplomb, and if you really need complex testing, its big sister is-deeply is standing at the wayside, ready to handle complex hash-array combinations.

Laziness and Impatience

Sometimes you’ve got a huge string, and you only need to check just a little bit of it.

ok 'Lake Char­gogg­a­gogg­man­chaugg­a­gogg­chau­bun­a­gung­a­maugg' ~~ 'manchau', 'my side';

You can certainly use the ~~ operator here. Just like ‘1 + 1 == 2’, though, your intent might not be clear. You can use the like method to make your intentions clear.

like 'Lake Char­gogg­a­gogg­man­chaugg­a­gogg­chau­bun­a­gung­a­maugg',
     /manchau/, 'my side';

and not have the ~~ dangling over the side of your boat.

DRYing out

After spending some time on and in beautiful Lake Char­gogg­a­gogg­man­chaugg­a­gogg­chau­bun­a­gung­a­maugg, you probably want to wring your clothes out. Test files tend to grow, especially regression tests. You might find yourself writing

is sprintf( "%s", '1' ), '1', "%s formats numbers";
is sprintf( "%s", '⅑' ), '⅑', "%s formats fractions";
is sprintf( "%s", 'Ⅷ' ), 'Ⅷ', "%s formats graphemes";
is sprintf( "%s", '三' ), '三', "%s formats CJKV";

That’s fine, copying and pasting (especially from StackOverflow) is a time-honored tradition, nothing wrong with that. Consider though, what happens when you add more tests with “%d” instead of “%s”, and since all of those strings are numbers, you just copy and paste the block, change “%s” to “%d” and go on.

is sprintf( "%s", '1' ), '1', "%s formats numbers';
# ...

is sprintf( "%d, '1' ), '1', "%d formats numbers';
# ...

So now you’ve got two sets of tests with the same names. Rather than editing all of the new “%d” tests, wouldn’t it be nice if we didn’t have to repeat ourselves in the first place?

subtest '%s', {
    is sprintf( "%s", '1' ), '1', "formats numbers";
    is sprintf( "%s", '⅑' ), '⅑', "formats fractions";
    is sprintf( "%s", 'Ⅷ' ), 'Ⅷ', "formats graphemes";
    is sprintf( "%s", '三' ), '三', "formats CJKV";
};

Now you just need to edit things in two places rather than three. If this has whetted your appetite for testing, I’d encourage you as well to check out Test All The Things at my personal site for more advanced testing paradigms and more advanced Perl 6 code. Also don’t forget to stay tuned for tomorrow’s Perl 6 Advent posting!

Thank you, and happy hacking!

DrForr aka Jeff Goff, The Perl Fisher