At my day job, I maintain a legacy Perl code base. When I started, there were very few tests. Over the years I’ve been adding mostly integration tests, since the code base is essentially a collection of scripts, with lots of logic. There are very few modules.
I’m now tasked to improve a feature of one of the programs in this code base. The functionality that needs improvement is spread across two functions. These functions are several hundred lines. The code uses regular expressions to find certain features present in report. At times it fails, because the format of the report has changed through the years.
To test these two functions, which only exist in the script itself, I’m using two techniques. The first is to turn the script into a module that can also run as a script. The second is to override a CPAN module by creating a clone of it.
The technique of turning a script into a module is nicely covered here https://perlmaven.com/modulino-both-script-and-module. I only needed to change/add about a dozen lines. In short, any code that isn’t part of subroutine is wrapped into one. The subroutine can have any name, but in this case, I’ve called it main. Then the body of the script has this line:
main() if not caller();
The script itself had lots of globals, so those were left outside of the main function and declared with our
. One final detail is to add a package line to the script. This keeps the script/module in its own namespace, providing isolation.
Before:
#!/usr/bin/perl
use DBI;
use Log::Log4perl;
use strict;
use warnings;
my $DBH;
my $LOGGER;
# other globals
Log::Log4perl::init(...);
$LOGGER=Log::Log4perl->get_logger(...);
$DBH=DBI->connect("DBI::mysql:...", ...);
process();
# more code
sub process {
...
}
After:
#!/usr/bin/perl
package process_reports;
use DBI;
use Log::Log4perl;
use strict;
use warnings;
our $DBH;
our $LOGGER;
# other globals
main() if not caller();
sub main {
Log::Log4perl::init(...);
$LOGGER=Log::Log4perl->get_logger(...);
$DBH=DBI->connect("DBI::mysql:...", ...);
process();
# more code
}
sub process {
...
}
With these changes in place, it is now possible to load the script as a module in a test. Each of the subroutines in the script will be callable, allowing much better testing. My next post will cover the second technique of overriding system modules.