Testing legacy Perl code – part 2

When Perl loads a module, it looks for the module in the subdirectories listed in the special Perl variable @INC. The first module it finds, it uses. Using this knowledge, a programmer can craft his own implementation of a module to substitute for the real module. In a test, the @INC can be manipulated by setting the environment variable PERL5LIB or by using the -I parameter when using prove.

jeffs-mbp:aws-npk jeff$ perl -V
Summary of my perl5 (revision 5 version 30 subversion 0) configuration:
...
  @INC:
    /Users/jeff/perl5/perlbrew/perls/perl-5.30.0-mojo/lib/site_perl/5.30.0/darwin-2level
    /Users/jeff/perl5/perlbrew/perls/perl-5.30.0-mojo/lib/site_perl/5.30.0
    /Users/jeff/perl5/perlbrew/perls/perl-5.30.0-mojo/lib/5.30.0/darwin-2level
    /Users/jeff/perl5/perlbrew/perls/perl-5.30.0-mojo/lib/5.30.0

jeffs-mbp:aws-npk jeff$ PERL5LIB=$PWD/lib perl -V
...
  @INC:
    /Users/jeff/projects/aws-npk/lib
    /Users/jeff/perl5/perlbrew/perls/perl-5.30.0-mojo/lib/site_perl/5.30.0/darwin-2level
    /Users/jeff/perl5/perlbrew/perls/perl-5.30.0-mojo/lib/site_perl/5.30.0
    /Users/jeff/perl5/perlbrew/perls/perl-5.30.0-mojo/lib/5.30.0/darwin-2level
    /Users/jeff/perl5/perlbrew/perls/perl-5.30.0-mojo/lib/5.30.0

By setting PERL5LIB to $PWD/lib, any modules found in the directory /Users/jeff/projects/aws-npk/lib will be used before any other modules.

Overriding a module that has lots of methods would be tedious and probably error prone. Perl has a solution for that, called AUTOLOAD. If a module has this method, then Perl will call it when a method does not exist in the module. Here is my implementation of DBD::mysql using this feature:

package DBD::mysql;

use Data::Dumper;
use strict;

sub new {
    my $class = shift;
    my %args = @_;

    return bless { LOG     => undef,
                   VERSION => "test DBD::mysql 1.0", 
                   %args}, $class;
}

sub display {
    my $self = shift;
    my $function = shift;
    my $content = shift;

    push(@{$self->{LOG}}, "$function: $content");

    if($self->{debug} == 1) {
        print("# $function:\n", "# $content\n");
    }
}

sub prepare {
    my $self = shift;
    my @args = @_;

    $self->display(Dumper(\@args));
    return $self;
}

sub fetchrow_arrayref {
    my $self = shift;
    my @args = @_;

    $self->display(Dumper(\@args));
    return [];
}

sub AUTOLOAD {
    my $self = shift;
    my @args = @_;

    our $AUTOLOAD;

    $self->display($AUTOLOAD, "@args");
}

1;

This module only defines two methods that are part of DBI::DBD, prepare and fetchrow_arrayref. Some code may require more to be defined, but in this case, the program I was testing only needed these two. prepare needed to return a reference to the object itself, because prepare normally returns a statement handle object, but Perl does not care as long as the object has the needed methods that are called upon it. fetchrow_arrayref returns an empty reference. All other methods are handled by the AUTOLOAD function. Most of the methods call the display function, which prints out the method and its arguments if the object was initialized with debug = 1.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.