Test::Class::MOP now has an 'is testcase' trait

When I previously blogged about Test::Class::MOP, I showed the bare basics of a test. Per a suggestion from Toby Inkster, I've now added an is testcase trait for methods. Unlike Test::Class::Moose, test methods no longer need to start with test_. Instead, you can do something like this:

method simple_test($report) is testcase {
    $report->plan(1);
    is $self->test_fixture->full_name, 'Bob Dobbs',
        'Our full name should be correct';
}

Obviously there's a lot going on there, so I'll explain a bit more about this.

Let's say we have a very simple Moose class (I'm using a Moose class for the code to make it clear that Test::Class::MOP is not just for testing MOP code):

package Person;
use Moose;
has [qw/first_name last_name/] => ( is => 'ro' );

sub full_name {
    my $self = shift;
    return join ' ' => $self->first_name, $self->last_name;
}

And here's a moderately advanced test for it:

use mop;

  class TestsFor::Person
extends Test::Class::MOP
   with Test::Class::MOP::Role::AutoUse {
    use Test::Most;
    has $!test_fixture is rw;

    # XXX bare return required due to this bug:
    # https://github.com/stevan/p5-mop-redux/issues/148
    method extra_constructor_args { return }

    method test_setup($report) {
        $self->next::method($report);
        $self->test_fixture($self->class_name->new(
            first_name => 'Bob',
            last_name  => 'Dobbs',
            $self->extra_constructor_args,
        ));
    }

    method simple_test($report) is testcase {
        $report->plan(1);
        is $self->test_fixture->full_name, 'Bob Dobbs',
            'Our full name should be correct';
    }
}

The class declares our test class name. The extends says that this class inherits from Test::Class::MOP (side note: p5-mop is single inheritance only. Use roles or delegation if you want to share behavior). The with Test::Class::MOP::Role::AutoUse says "strip the prefix from the class name and use the resulting package. For those who prefer less magic, it's simple:

class TestsFor::Person extends Test::Class::MOP {
    use Person ...

Everything else should be fairly clear. That seems like an awful lot for a single test, but those who are used to Test::Class (or xUnit testing in general) know how well this quickly scales up to handle large test suites.

And subclassing that test (assumes we have a Person::Employee class):

use mop;

class TestsFor::Person::Employee extends TestsFor::Person {
    use Test::Most;

    method extra_constructor_args {
        return ( employee_number => 666 );
    } 

    method simple_test($report) is testcase {
        $self->next::method($report);
        $report->plan(1);
        is $self->test_fixture->employee_number, 666,
          '... and we should get the correct employee number';
    } 
}

I would like to eventually extend the is testcase trait to allow this:

method order_items_consistent($report) is testcase( tests => 7 ) {
     ...
}

But so far, I haven't figure out how to do that and get the plan back to the method level. I think keeping this simple at first is a better way to go.

Adding the is testcase trait was done via a custom metaclass I wrote and then implemented with:

class Test::Class::MOP meta TestClassMeta {
    ...
}

The metaclass itself is clumsy because I was having some issues with namespaces, but since it's sufficiently encapsulated, I hope to fix that in the the future. In fact, if I can figure out how to make the optional plans work, I may just dump the $report argument entirely. That should make things even cleaner.

Test::Class::MOP is should hopefully soon be integrated into p5-mop-redux's Travis CI setup, letting me know quickly if changes to the mop break my code.

Test::Class::MOP is available on github.

Leave a comment

About Ovid

user-pic Freelance Perl/Testing/Agile consultant and trainer. See http://www.allaroundtheworld.fr/ for our services. If you have a problem with Perl, we will solve it for you.