Apr 23, 2011

Every module has a test with Module::Pluggable

This is a sort of meta test, checking that every module under a chosen namespace has its own corresponding file test, following a naming convention .

I will spend few words on this, just showing you the code that works for me and can be adapted to your needs ... so lets talk more Perlish than English :^)

I used the core module Module::Pluggable to get all packages under, for instance, the PNI::Node namespace where there will be all my PNI "nodes" i.e. plugins implementing the PNI::Node interface (that has simply two abstract method init and task ) .

I choose the following naming convention :
the PNI::Node::Foo::Bar::My_node_name
has
the test _node-foo-bar-my_node_name.t

Here it is a PNI::Find package which holds a static method called nodes that returns a list containing all the packages under the PNI::Node namespace . PNI::Find is a singleton, but don't care about that by now, it is just a convenience to call PNI::Find->nodes from my meta test ( see below ) .
package PNI::Find;
use strict;
use warnings;
our $VERSION = '0.12';
use Module::Pluggable search_path => 'PNI::Node', require => 1, inner => 0;

# singleton
my $self;

sub new {
    if ( not defined $self ) {
        $self = bless \__PACKAGE__, __PACKAGE__;
    }
    return $self;
}

sub nodes {

    my @nodes;
    for my $module ( $self->plugins ) {
        if ( $module->isa('PNI::Node') ) {
            push @nodes, $module;
        }
    }
    return @nodes;
}

1;


The following test script, called every_node_has_a_test.t, checks that under the t directory there is a test file for every node ( or plugin in your case ) .

use strict;
use Test::More;
use File::Spec;
use PNI::Find;

if ( not $ENV{TEST_AUTHOR} ) {
    my $msg = 'Author test.  Set $ENV{TEST_AUTHOR} to a true value to run.';
    plan( skip_all => $msg );
}

my $find = PNI::Find->new;

my @core_nodes_dirs;
my $node_dir_path = File::Spec->catfile(qw(lib PNI Node));
opendir my ($node_dir_handle), $node_dir_path;
for my $entry ( readdir $node_dir_handle ) {

    # looking or dirs starting  with an uppercase letter
    next unless -d File::Spec->catfile( $node_dir_path, $entry );
    next unless $entry =~ /^[A-Z][a-z]+/;

    push @core_nodes_dirs, $entry;
}

my $core_nodes_dirs_regexp = join '|', @core_nodes_dirs;

for my $node_class ( $find->nodes ) {

    # skip check on nodes not included in PNI
    next if $node_class !~ /^PNI::Node::($core_nodes_dirs_regexp)/;

    # naming convention for test of PNI::Node::Foo::Bar is _node-foo-bar.t
    my $node_test = $node_class;
    $node_test =~ s/^PNI:://;
    $node_test =~ s/::/-/g;
    $node_test = lc "_$node_test.t";
    my $test_path = File::Spec->catfile( 't', $node_test );
    ok -e $test_path, "$node_class has a test";
}

done_testing;

Conclusion

Test driven development is a must, that's why I think it is necessary to check that there is at least a test for every module specially if you can't know a priori how much will be populated a namespace, for example if you decide to have plugins .

Next step would be to code some helper to create your modules ... mmhh I'm going to open some good CPAN module like Mojolicious and steal some code :-)

No comments:

Post a Comment