Hacking Thy Fearful Symmetry

Hacker, hacker coding bright
Powered by a Gamboling Beluga

Win Some, Test::Some

created: November 4, 2015

Gather 'round, children, for I am about to bedazzle you with my next trick...

First, look at that test file.


use strict;
use warnings;

use Test::More;

subtest eeny  => sub { pass };
subtest meeny => sub { fail };
subtest mo    => sub { pass };

See? Nothing there. Just regular subtests. And, indeed, running it causes no surprises.


$ perl example.pl 
    # Subtest: eeny
    ok 1
    1..1
ok 1 - eeny
    # Subtest: meeny
    not ok 1
    #   Failed test at example.pl line 9.
    1..1
    # Looks like you failed 1 test of 1.
not ok 2 - meeny
#   Failed test 'meeny'
#   at example.pl line 9.
    # Subtest: mo
    ok 1
    1..1
ok 3 - mo
1..3
# Looks like you failed 1 test of 3.

Now, to be truly magical, imagine that the test file is much, much bigger and all subtests are huge and slow as molasse, and that you're only interested in meeny right now. Wouldn't be cool to just run that one? Well... Follow my hands, kids...


$ perl -MTest::Some=meeny example.pl 
    # Subtest: eeny
    1..0 # SKIP Test::Some skipping
ok 1 # skip Test::Some skipping
    # Subtest: meeny
    not ok 1
    #   Failed test at example.pl line 9.
    1..1
    # Looks like you failed 1 test of 1.
not ok 2 - meeny
#   Failed test 'meeny'
#   at lib/Test/Some.pm line 89.
    # Subtest: mo
    1..0 # SKIP Test::Some skipping
ok 3 # skip Test::Some skipping
1..3
# Looks like you failed 1 test of 3.

Wait, I'm not done! Don't care about the final number of tests and want all the uninteresting tests to not only be skipped, but disappear altogether? Well... Abra...

cada...

BRA


$ perl -Ilib -MTest::Some=~,meeny example.pl 
    # Subtest: meeny
    not ok 1
    #   Failed test at example.pl line 9.
    1..1
    # Looks like you failed 1 test of 1.
not ok 1 - meeny
#   Failed test 'meeny'
#   at lib/Test/Some.pm line 89.
1..1
# Looks like you failed 1 test of 1.

Not magic after all, only spontaneous etheric transmutation

As all good magic tricks, this one relies on simplicity and sleight of hand. My new module, Test-Some, wraps Test::More's subtest in a function that skip/bypass the tests if they aren't part of the wanted whitelist.

The whitelist can be defined in a few ways.

Plain old subtest names


# will run subtests 'foo' and 'bar'.
use Test::Some 'foo', 'bar';

Anything but that one

You can go negative instead and specify tests you don't want to see run.


# run all tests but 'foo'
use Test::Some '!foo';

Note that a subtest is run if it matches any item in the whitelist, so


use Test::Some '!foo', '!bar';

will run all tests as foo is not bar and vice versa.

Regular expressions

It just wouldn't be all that fun if we couldn't use regular expressions in there as well.


# run all tests beginning with 'foo'
use Test::Some '/foo';

# or

use Test::Some qr/foo/;

Incidentally, the cutesy /foo instead of plain old qr/foo/ is there so that the shell invocation perl -MTest::Some=/foo will work too.

Tags

We can also flag that we only want the subtests with the given tags to be run


use Test::Some ':basic';

"Wait", I hear you say, "did you say tag?" Yes I did, my friend, yes I did. Test::More's subtest takes two arguments, the name of the tests and the coderef holding the test itself. If you add trailing values, the original subtest will ignore them. But the Test::Some wrapper will consider them to be tags.


use Test::Some ':basic';  # will only run subtest 'foo'

subtest foo => sub { .... }, 'basic';

subtest bar => sub { .... }, 'complex', 'auth';

Tadah! Instant, backward-compatible tags for tests! How's that for a Prestige?

Now, I know that the sub { ... } part can get pretty big, and the tags could get lost in the noise. I don't have a perfect answer for that, but I can at least offer a mitigating workaround:


subtest foo => \&_foo, 'tag1', 'tag2';
sub _foo {
    ok something(), "weeee!";
    ...
}

Mix them up

Guess what? We can also mix those tags as we want.


# run all tests with no tags beginning with 'foo'
use Test::Some '!:/^foo';

Or just do whatever you want

Names, tags and regular expressions are not enough for you? Sheesh But okay, you can also pass a coderef as a condition. The name of the test and its tags will be passed to it as $_ and %_.


use DateTime::Functions qw/ now /;

use Test::Some sub {
    # only run important tests on Friday
    now()->day_of_week != 5 or $_{important};
};

Oh yeah, and mum about the whole thing

Almost forgot: the filter '~' means "don't SKIP the tests, but bypass them altogether". It'll mess up the final number of tests if the test is using plan tests => $n instead of `done_testing(), but if blessed silence is what you want, ~ will make you happy.

Enjoy!

comments powered by Disqus

About the author

Yanick Champoux
Perl necrohacker, ACP writer, orchid lover. Slightly bonker all around. Works at Infinity Interactive