Still More Perl Lightning Articles
by Phil Crow, Josh McAdams, Steven Schubiger, chromatic
|
Pages: 1, 2, 3, 4
Scriptify Your Module
Josh McAdams
Recently during an MJD talk at Chicago.pm, I saw a little Perl trick that was so amazingly simple and yet so useful that it was hard to believe that more mongers in the crowd hadn't heard of it. The trick involved taking your module and adding a driver routine to it so the module could run as a script.
To illustrate, start with an example module that contains two utility subroutines that convert weights between pounds and kilograms. The subroutines accept some number and multiplies it by a conversion factor.
package WeightConverter;
use strict;
use warnings;
use constant LB_PER_KG => 2.20462262;
use constant KG_PER_LB => 1/LB_PER_KG;
sub kilograms_to_pounds { $_[0] * LB_PER_KG; }
sub pounds_to_kilograms { $_[0] * KG_PER_LB; }
Assuming that the real module has a little error checking and POD, this module would serve you just fine. However, what if you decided that we needed to be able to easily do weight conversions from the command line? One option would be to write a Perl script that used WeightConverter. If that seems like too much effort, there is a one-liner that would do conversions.
perl -MWeightConverter -e 'print WeightConverter::kilograms_to_pounds(1),"\n"'
This would do the trick, but it is a lot to remember and isn't very fun to type. There is a lot of benefit available from saving some form of script, and believe it or not, the module can hold that script. All that you have to do is write some driver subroutine and then call that subroutine if the module is not being used by another script. Here is an example driver for WeightConverter.
This example driver script just loops through the command-line arguments and tries to find instances where the argument contains either a k or p equal to some value. Based on whether or not you are starting with pounds or kilograms, it calls the appropriate subroutine and prints the results.
sub run {
for (@ARGV) {
if(/^[-]{0,2}(k|p)\w*=(.+)$/) {
$1 eq 'k' ?
print "$2 kilograms is ", kilograms_to_pounds($2), " pounds\n" :
print "$2 pounds is ", pounds_to_kilograms($2), " kilograms\n" ;
}
}
}
Now all that is left is to tell the module to run the run subroutine if someone has run the module on its own. This is as easy as adding one line somewhere in the main body of the module.
run unless caller;
All this statement does is execute the run subroutine unless the caller function returns a value. caller will only return true if WeightConverter is being used in another script. Now, this module is usable in other scripts as well as on the command line.
$> perl WeightConverter.pm -kilos=2 -pounds=145 -k=.345
2 kilograms is 4.40924524 pounds
145 pounds is 65.7708937051548 kilograms
.345 kilograms is 0.7605948039 pounds
Mocks in Your Test Fixtures
by chromatic
Since writing Test::MockObject,
I've used it in nearly every complex test file I've written. It makes my life
much easier to be able to control only what I need for the current group of
tests.
I wish I'd written Test::MockObject::Extends
earlier than I did; that module allows you to decorate an existing object with
a mockable wrapper. It works just as the wrapped object does, but if you add
any mocked methods, it will work like a regular mock object.
This is very useful when you don't want to go through all of the overhead of
setting up your own mock object but do want to override one or two methods.
(It's almost always the right thing to do instead of using
Test::MockObject..)
Another very useful test module is Test::Class. It
takes more work to understand and to use than Test::More, but
it pays back that investment by allowing you to group, reuse, and organize
tests in the same way you would group, reuse, and organize objects in your
code. Instead of writing your tests procedurally, from the start to the end of
a test file, you organize them into classes.
This is most useful when you've organized your code along similar lines.
If you have a base class with a lot of behavior and a handful of subclasses
that add and override a little bit of behavior, write a
Test::Class-based test for the base class and smaller tests
that inherit from the base test for the subclasses.
Goodbye, duplicate code.
Fixtures
Test::Class encourages you to group related tests into test
methods. This allows you to override and extend those groups of tests in
test subclasses. (Good OO design principles apply here; tests are still
just code, after all.) One of the benefits of grouping tests in this way is
that you can use test fixtures.
A test fixture is another method that runs before every test method. You can use them to set up the test environment--creating a new object to test, resetting test data, and generally making sure that tests don't interfere with each other.
A standard test fixture might resemble:
sub make_fixture :Test( setup )
{
my $self = shift;
$self->{object} = $self->test_class()->new();
}
Assuming that there's a test_class() method that returns
the name of the class being tested, this fixture creates a new instance
before every test method and stores it as the object
attribute. The test methods can then fetch this as normal.
Putting Them Together
I recently built some tests for a large system using
Test::Class. Some of the tests had mockable features--they
dealt with file or database errors, for example. I found myself creating a
lot of little Test::MockObject::Extends instances within most
of the tests.
Then inspiration struck. Duplication is bad. Repetition is bad. Factor it out into one place.
The insight was quick and sudden. If
Test::MockObject::Extends is transparent (and if it isn't,
please file a bug--I'll fix it), I can use it in the test fixture all the
time and then be able to mock whenever I want without doing any setup. I
changed my fixture to:
sub make_fixture :Test( setup )
{
my $self = shift;
my $object = $self->test_class()->new();
$self->{object} = Test::MockObject::Extends->new( $object );
}
The rest of my code remained unchanged, except that now I could delete several identical lines from several test methods.
Do note that, for this to work, you must adhere to good OO design
principles in the code being tested. Don't assume that ref is
always what you think it should be (and use the isa() method
instead).
Sure, this is a one-line trick, but it removed a lot of busy work from
my life and it illustrates two interesting techniques for managing tests.
If you need simpler, more precise mocks, use
Test::MockObject::Extends. If you need better organization and
less duplication in your test files, use Test::Class. Like all
good test modules, they work together almost flawlessly.
You must be logged in to the O'Reilly Network to post a talkback.
Showing messages 1 through 3 of 3.
- Test::Class's Own Tests Fail
2006-07-18 23:00:48 gordonmccreight [Reply]
I like the idea of using Test::Class, and can see the benefit, but I can't get it to pass its own tests in the CPAN shell, which of course is a bit disconcerting for a Test module. My guess is that since it is over a year and a half old the Test::Builder API has shifted a bit underneath it, but I'm hardly expert enough to know for sure. I've looked at the RT outstanding bugs for it, and there are a few which closely resemble my own issue. chromatic, did you just force install it, or have you had it on your system for so long that it didn't complain when you installed it? (Or alternatively, did it install recently with no problems for you?)- Test::Class's Own Tests Fail
2006-10-04 04:01:23 adrianh [Reply]
Fixed now :-)
- Test::Class's Own Tests Fail
2006-07-21 13:54:52 adrianh [Reply]
Erm... yes... I should fix that shouldn't I... :-)
Excuse me while I go type...
Adrian
- Test::Class's Own Tests Fail



