Perl Code Kata: Testing Imports
Perl Taint Test Kata introduced the idea of Perl Test Kata, small exercises designed to improve your understanding of Perl and your ability to write test-driven code. This article is the second in the series.
Import Testing Kata
Perl 5 added the ideas of namespaces and modules, making code reusable and easier to maintain. To allow convenience, it also added an importing mechanism to put code from a module into the current namespace.
Behind the scenes, when you use
a module, Perl loads it from disk and, if successful, calls the special method import()
. By convention, this generally imports functions. Much of the time, import()
mundanely installs subroutines into the current namespace. That’s why so many modules use Exporter to provide a default import()
.
However, it’s also a general module-loading hook that can perform many different types of manipulations. For example, Filter::Simple allows the use of source filters to transform code that looks entirely unlike Perl into valid code in the using module. Other modules change their behavior depending on any arguments passed to import()
. This includes Test::More and Test::Simple, which interpret their arguments as information about how many tests to run.
use Test::More 'no_plan';
# or
use Test::More tests => 100;
This feature is both powerful and important. Because of its importance, it needs good tests. Because of its power and flexibility, it may seem difficult to test an import()
well. Here are three sample implementations for you to practice testing.
Basic Exporting
package Basic::Exports;
use strict;
use base 'Exporter';
use vars '@EXPORT';
@EXPORT = qw( foo bar );
sub foo { 'foo' }
sub bar { 'bar' }
1;
The tests should check that using Basic::Exports exports foo()
and bar()
to the appropriate namespace and that they return the appropriate values. Another test is that the code use Basic::Exports ();
exports neither function.
Optional Exports
package Optional::Exports;
use strict;
use base 'Exporter';
use vars '@EXPORT_OK';
@EXPORT_OK = qw( foo bar baz );
sub foo { 'foo' }
sub bar { 'bar' }
sub baz { 'baz' }
1;
The tests should check that Optional::Exports exports nothing by default and only those functions named, if there are any.
Load-time Behavior
A few modules have curious behavior. My Pod::ToDemo behaves differently when invoked from the command line versus when used within a module. This makes it substantially more difficult to test. Rather than make you reinvent the tests there, here’s a simpler custom import()
that does different things based on its invocation. If invoked from the command line, it prints a message to standard output. If used from a module, it exports the same foo()
subroutine as before.
package Export::Weird;
use strict;
sub import
{
my ($package, undef, $line) = caller();
if ( $line == 0 )
{
print "Invoked from command-line\n";
}
else
{
no strict 'refs';
*{ $package . '::foo' } = sub { 'foo' };
}
}
1;
The only really tricky test here must exercise the behavior of the module when invoked from the command line. Assume that the documentation of the module suggests invoking it via:
$ perl -MExport::Weird -e 1
The next page explains some techniques for testing these examples. For best results, spend between 30 and 45 minutes working through the kata on your own before looking at the hints. For more information on how modules, use
, and require
work, see perldoc perlmod
and perldoc perlfunc
.
Tips, Tricks, and Suggestions
If you’ve worked your way through writing tests for the three examples, here are the approaches I would take. They’re not the only ways to test these examples, but they do work. First, here is some background information on what’s happening.
Reloading
To test import()
properly, you must understand its implications. When Perl encounters a use module;
statement, it executes a two-step process immediately:
BEGIN
{
require module;
module->import();
}
You can subvert both of these processes. To force Perl to reload a module, you can delete its entry from %INC
. Note that all of the keys of this special hash represent pathnames in Unix format. For example, even if you use Windows or VMS or Mac OS 9 or earlier, loading Filter::Simple successfully should result in %INC
containing a true value for the key of Filter/Simple.pm
. (You may also want to use the delete_package()
function of the Symbol module to clear out the namespace, though beware of the caveats there.) Now you can require
the module again.
Re-importing
Next, you’ll have to call import()
manually. It’s a normal class method call, however, so you can provide all of the arguments as you would to a function or method call.
You can also switch packages, though make sure that you qualify any calls to Test::* module functions appropriately:
package Some::Other::Package;
module->import( @args );
main::ok( 1, 'some test label' );
# or
::ok( 1, 'some test label' );
Testing Exports
There are at least two techniques for checking the import of functions. One is the use of the defined
keyword and the other is through the can()
class method. For example, tests for Example #1 might be:
use_ok( 'Basic::Exports' );
ok( defined &foo, 'module should export foo()' )
ok( __PACKAGE__->can( 'bar' ), '... and should export bar()' );
To test that these are the right functions, call them as normal and check their return values.
By the way, the presence of the __PACKAGE__
symbol there allows this test to take place in other namespaces. If you haven’t imported the ok()
test function into this namespace, remember to qualify it, import it manually, or alias it so that the test program will itself run. (It may fail, which is fine, but errors in your tests are difficult and embarrassing to fix.)
Testing Non-Exports
It’s difficult to prove a negative conclusively, but if you reverse the condition of a test, you can have good confidence that the module hasn’t provided anything unwanted.
use_ok( 'Optional::Exports' );
ok( ! __PACKAGE__->can( 'foo' ),
'module should not export foo() by default' );
The only tricky part of the tests here is in trying to import functions again. Call import()
explicitly as a class method of the module. Switching packages within the test can make this easier; you don’t have to unload the module if you do this.
Testing Weird Exports
The easist way to test an import()
function that relies on command-line invocation or produces weird side effects that you may not want to handle in your current program is to launch it as a separate program. There are plenty of options for this, from system
to fork
and exec
to tricks with pipes and shell redirection. IPC::Open3 is one good approach, if you want to use it in your test suite:
#! perl
use strict;
use warnings;
use blib;
use IPC::Open3;
use Test::More tests => 3;
use_ok( 'Export::Weird' );
my $pid = open3(
undef, my $reader, undef,
$^X, '-Mblib', '-MExport::Weird', '-e', '1'
);
my @out = <$reader>;
is( @out, 1,
'cli invocation should print one line' );
is( $out[0], "Invoked from command-line\n",
'... with the right message' );
$^X
represents the path to the Perl binary currently executing this program. The -Mblib
switch loads the blib
module to set @INC
in the program appropriately. Depending on how you’ve set up your directories and invoke this program, you may have to change this. The other commands follow the invocation scheme given in Example #3.
Conclusion
You should now have several ideas on how to test import()
methods of various kinds. For more details, read the tests of Pod::ToDemo or Test::Builder, which play strange games to achieve good test coverage.
If you’ve found a differently workable approach, I’d like to hear from you. Also, if you have suggestions for another kata (or would like to write one), please let me know.
chromatic is the author of Modern Perl. In his spare time, he has been working on helping novices understand stocks and investing.
Tags
Feedback
Something wrong with this article? Help us out by opening an issue or pull request on GitHub