People like to find excuses to avoid writing tests for their code. One
of the most common goes something like, "It's not feasible to test this,
because it relies on external objects" - CGI code, code using the
Apache request object, TCP/IP servers, and so on.
The Test::MockObject module makes it much easier to isolate code that
uses such objects. For example, if your code uses a CGI object, then you
could fiddle with query strings and faked STDIN, trying to
persuade CGI.pm to produce testable values. It's easier to use
Test::MockObject to create an object that looks and behaves like
a CGI object -- but that is completely under your control.
This comes in handy in large software projects, where objects encapsulate tremendous amounts of hidden behavior. If your application follows good design principles by hiding complexity behind well-defined interfaces, then you can replace nearly any component with its interface equivalent. (The internals, of course, are free to change. That's why this is possible.) Often, it's sufficient just to accept certain arguments and to return specific values.
Using a mock object, you can test whether the code in question uses a particular
interface correctly. It's possible to do this by hand, but Test::MockObject
has several utility methods to add fake methods and verify calls. It
integrates with Test::Builder, so it works correctly with Test::Simple,
Test::More, and their cousins.
The Background You Need to Know
I assume that you are already familiar with Test::More. Perhaps you've read
Test::Tutorial, which comes with the Test::Simple distribution. You may also
have read my earlier introduction to the subject.
If not, then you may wish to do so. (My roommate tried it out of order and hurt his
head. If you fare any better, then the Perl QA group is interested in your natural
talent!)
My example comes from the unit tests for the Everything Engine.
I chose it for two reasons. First, it's a
project near and dear to my heart. Second, it needs more users, testers and
developers. More importantly, it's where I came up with the ideas that led to
Test::MockObject.
My colleague on the Engine, Darrick Brown, devised a clever technique he dubbed
Form Objects. These are used to bind menu choices to nodes. (Everything in
Everything is a node. It's the base unit of data and behavior.) Form objects
control the creation of HTML widgets, verify submitted data and ultimately
update nodes. They all inherit strongly from Everything::FormObject and
operate on node objects, so they're an ideal candidate for mock objects.
Mock Objects
This article focuses on white-box unit testing with mock objects. "White box" testing means that you're allowed and encouraged to look at the internals of the thing being tested. This is scarily possible, with Perl. By contrast, "black box" testing happens when you cannot know the internal details: you just know allowed inputs and the expected outputs. (If you don't know that, then you can't do much testing.)
Unit testing, of course, is testing individual components of the program in isolation, as far as possible. This is different from integration testing, which exercises the program as a whole, and acceptance testing, which explores the desired end-user behavior of the program. No type of testing is better or worse than any other. Done properly, they are complementary: Unit tests are capable of exploring internal behaviors that are difficult to prove with acceptance tests; integration tests demonstrate the interoperability between different components that unit tests usually cannot guarantee.
The points of a mock object is to isolate the units being tested from their dependencies, and to give testers more complete control over the testing environments. This follows from standard programming principles: If you can fake the interfaces your unit relies on, then you can control and monitor its behavior.
Perl makes this particularly easy.
The Example
I'm writing a test for Everything::HTML::FormObject::AuthorMenu. This class
represents an HTML select box used to set the author of a node. It has two
methods. genObject() produces the HTML necessary for the select widget. It
is called when the Engine builds a page to display to the user. The other
method, cgiVerify(), is called when receiving data from a user submission.
It checks to see whether the requested author exists and has write access to the
node.
Looking Inside
The module begins rather simply:
use strict;
use Everything;
use Everything::HTML;
use Everything::HTML::FormObject;
use vars qw(@ISA);
@ISA = ("Everything::HTML::FormObject");
Testing this is all very easy. I'd like to make sure that the module continues
to load all of these modules, so I need some way to catch their use. (Don't
laugh -- I've forgotten to load important modules before, causing really tricky
errors. It's better to be precise. Now you can laugh.) Because use calls
import() behind the scenes, if I can install my own import() before AuthorMenu
is compiled, then I can test that these modules are actually used. As a side
benefit, doing so prevents these other classes from loading, making it easier
to mock them. The test starts:
package Everything::HTML::FormObject::AuthorMenu;
use vars qw( $DB );
package main;
use Test::More 'no_plan';
Because I'm faking the other packages, anything the true modules would normally
import will not be imported. The only thing that really matters at this point
is the $DB object, exported from the Everything package. (I'm cheating a
little bit. I know I'll use it later, and I know where it's defined and how
it's used. At this point, I should probably say, ``The module will fail to
compile unless I fake it here,'' and leave it at that.)
Because I'm not ready to implement $DB, I just switch to the package to be
tested and declare it as a global variable. When the package is compiled, it's
already there, so it'll compile successfully. Then I return to the main
package so I don't accidentally clobber anything important and use the testing
module.
my @imports;
for ( 'Everything', 'Everything::HTML',
'Everything::HTML::FormObject') {
no strict 'refs';
*{ $_ . '::import' } = sub {
push @imports, $_[0];
};
(my $modpath = $_) =~ s!::!/!g;
$INC{ $modpath . '.pm' } = 1;
}
Here's where it starts to get tricky. Because I want to ensure that these
three modules are loaded correctly, I have to make Perl think they're already
loaded. %INC comes to the rescue. When you use() or require() a
module successfully, Perl adds an entry to %INC with the module pathname,
relative to one of the directories in @INC. That way, if you use() or
require() the module again, then Perl knows that it's already been loaded.
As mentioned before, my preferred way to check that a module is loaded is to
trap all calls to import(). That's why I install fake import subroutines.
They simply save the name of the package by which they're identified. The
tests are simple to write:
use_ok( 'Everything::HTML::FormObject::AuthorMenu' );
is( $imports[0], 'Everything', 'Module should use
Everything' );
is( $imports[1], 'Everything::HTML',
'Module should use Everything::HTML' );
is( $imports[2], 'Everything::HTML::FormObject',
'Module should use
Everything::HTML::FormObject' );
Because use_ok() fires at runtime, it's safe not to wrap this whole section
in a BEGIN block. (If you're curious about this, see what perlfunc has to
say about use() and its equivalent.)
That works, but it's a little messy and uses some tricks that might scare (or at least confuse) the average Perl hacker. One of the goals of the Test::* modules is to do away with the ``evil black magic'' you'd normally have to use. So, now I show you a more excellent way.
Making That Last Bit Easier
Having found myself writing that code way too often (at least twice), I added
it to Test::MockObject. Using the module, the corresponding loop is now:
my @imports;
for ( 'Everything', 'Everything::HTML',
'Everything::HTML::FormObject') {
Test::MockObject->fake_module(
$_,
import => sub { push @imports,
$_[0] }
);
}
Behind the scenes, the module does exactly what the loop did. The nice thing
is that you don't have to remember how to fake that a module is loaded or how
to test import(). It's already done and it's nicely encapsulated in a
module. (I inadvertently drove this point home to myself when writing this
section. It turns out that version 0.04 of Test::MockObject populated %ENV
instead of %INC. I make that typo often. This time, it was in both the
module and the test. Get at least version 0.08, as this article has led to
bug fixes and new conveniences. :)
This isn't the most important feature of Test::MockObject, though. It's just a
convenient addition. At some point, it should probably be spun off into
Test::MockPackage or Test::MockModule. (Want to contribute?)
Testing an Actual Method
Once the module is loaded and ready, I like to test my methods in the
order in which they appear. This helps to keep the test suite and the module
somewhat synchronized. The first method is genObject(). It's fairly simple:
my $this = shift @_;
my ($query, $bindNode, $field, $name, $default) =
getParamArray(
"query, bindNode, field, name,
default", @_);
$name ||= $field;
my $html = $this->SUPER::genObject(
$query, $bindNode, $field, $name) .
"\n";
if(ref $bindNode)
{
my $author = $DB->getNode($$bindNode{$field});
if($author && $author->isOfType('user'))
{
$default ||= $$author{title};
}
elsif($author)
{
$default ||= "";
}
}
$html .= $query->textfield(-name => $name,
-default => $default,
-size => 15, -maxlength => 255,
-override => 1) . "\n";
return $html;
I can see several spots that need tests. First, I want to make sure that
getParamArray() is called with the proper arguments. (This function makes
it possible to pass parameters by position or in name => value pair
style, similar to
Sub::NamedParams.) Next,
I'll test that SUPER::genObject() is called correctly, with the proper
values. (This call looks odder than it is, due to the way the Engine handles
node inheritance. For a good time, read Everything::Node::AUTOLOAD(), or
take up bowling.) After that, there's the conditional statement. I'll have to
call the function at least three times to test the branches effectively. The
function ends with a textfield() call I want to test, and has a return value
where I can check some other things. It's not as complex as it looks.
One of the side benefits of testing is that you'll start to write smaller and smaller functions. This is especially evident if you write tests before you write code to pass them. Besides being easier to read and debug, this tends to produce code that's more flexible and much more powerful.
Having identified several things to test, I next write test names. These are short descriptions of the intent of the tests. When confronted with a piece of existing code, I usually try to figure out what kinds of things can break, and what the important behavior really is. With experience, you can look at a piece of well-written code and figure it out intuitively. There's room to be explicit when you're just starting, though.
# genObject() should call getParamArray()
# ... passing a string of desired parameters
# ... and its arguments, minus the object
# ... should call SUPER::genObject()
# ... passing the important parameters
# ... and should call textfield()
# ... with the important parameters
# ... returning its and its parents results
# ... if node is bound, should call getNode()
# ... on bound node field
# ... checking for an author node
# ... and setting the default to the author name
# ... or nothing, if it is not an author node
That's a pretty good rough draft. Going through the list reveals the need to make at least two more passes through the code. Getting this in the right order takes a little work, but once you know how to set up the mocking conditions correctly, it's really fast and easy. The best way I've found to handle this is just to jump right in.
can_ok( 'Everything::HTML::FormObject::AuthorMenu',
'genObject' );
First, I want to make sure this method exists. Why? It's part of the ``Do the simplest thing that could possibly work'' principle. Whenever I add a method, I first check to see whether it exists. It sounds too stupid to have any use, but this is a thing that could possibly break. First, I've been known to misspell method names occasionally. This'll catch that immediately. Second, it gives me a place to start and a test that passes with little work. That's a nice psychological boost that moves me on to the next test. I've kept this habit when writing tests for existing code.
Next, I want to test the call to getParamArray(). Since it's not a method, I
can't use a mock object. I'll have to mock it sideways. Though the function
comes from Everything.pm, it would normally be exported into this package.
I'll use a variant of the import()-mockery earlier:
my ($gpa, @gpa);
$mock->fake_module( $package,
getParamArray => sub { push @gpa, \@_; return $gpa }
);
I can count the elements of @gpa to see whether it was called, and pull the
arguments out of the array. $gpa allows me to control the output. The test
itself is easy to write:
my $result = genObject();
is( @gpa, 1, 'genObject() should call getParamArray' );
OK, it's a little easier to write than it should have been. If you're paying
attention, then you should wonder why the genObject() call works, as I'm still in
the main package and the method is in the class. I've just added a variable
with the tested package name, as well as an AUTOLOAD() function. I'm already
tired of typing the big long package name:
# near the start
use vars qw( $AUTOLOAD );
my $package = 'Everything::HTML::FormObject::AuthorMenu';
...
# way down at the end
sub AUTOLOAD {
my ($subname) = $AUTOLOAD =~ /([^:]+)$/;
if (my $sub = UNIVERSAL::can( $package,
$subname )) {
$sub->( @_ );
} else {
warn "Cannot call <$subname>
in ($package)\n";
}
}
With all of that infrastructure in place, it's a little disappointing to
realize that the test dies. Since I'm calling the method as a function,
there's no object on which to call SUPER::genObject(). This is easily
solved. Remember that $mock (a mock object) was created earlier? Here's one
bit of Perl's magic that drives some OO purists crazy, but makes it oh-so-easy
to test. A method call is a function call with a special first argument. If
$this, inside genObject(), can do everything that an
Everything::HTML::FormObject::AuthorMenu object can do, then it'll just work.
Hooray for polymorphism!
To make the SUPER::genObject() call pass, that call will also have to be
mocked. The method resolves to Everything::HTML::FormObject::genObject(),
so I'll add a function of the appropriate name and package. (This test code is
starting to look familiar. Again, Test::MockModule, anyone?)
my @go;
$mock->fake_module( 'Everything::HTML::FormObject',
genObject => sub { push @go, \@_;
return 'some html' }
);
Now I modify the genObject() call, passing in my mock object:
my $result = genObject( $mock );
I get further before things fail. Since there's nothing passed in for the
$query variable to hold, the textfield() call fails. Now I can finally
use my mock object to good effect. First, I'm going to change what
getParamArray() returns, using $package again to save on typing:
my (%gpa, @gpa);
$mock->fake_module( $package,
getParamArray => sub { push @gpa, \@_; return
@gpa{qw( q bn f n d )}}
);
Since AuthorMenu expects to receive its arguments in order, I'll create a hash
where I can store them. I might use more descriptive key names, but they seem
to make sense now. Next, I'll make sure that 'q' returns something
controllable. In this case, that means that it supports the textfield()
method and returns something sane:
$mock->set_always( 'textfield', 'more html' );
$gpa{q} = $mock;
I could create a new mock object for this, but since there's no name collision yet, it's not a big priority. Whether you do this is a matter of personal style.
For now, genObject() does not die, and all tests pass. Whew. Next up, I
test to see whether the first argument to getParamArray() is correct.
is( $gpa[0][0], 'query, bindNode, field, name, default',
'... requesting the appropriate arguments' );
It is, so I'll make sure that it passes along the rest of the method arguments, minus the object itself:
like( join(' ', @{ $gpa[0] }), qr/1 2 3$/,
'... with the method arguments' );
unlike( join(' ', @{ $gpa[0] }), qr/$mock/,
'... but not the object itself' );
Only the first of these fails, and that's because I'm not passing any other arguments to the method call. I'll modify it:
my $result = genObject( $mock, 1, 2, 3 );
This gives me nine tests that pass. I'm also following the test names fairly closely. (In between writing those and actually writing this code, a couple of days passed. Their similarities make me think I'm on the right track.)
Since the next piece of the code tries to load a bound node and I'm not passing
one in, I'll test to see that getNode() is not called. Since the call is
on the $DB object, I'll set it to the mock object. I'll also use the
called() method to make sure that nothing happens. For that to happen, I
need to mock getNode(). The code to implement all of this is pretty simple.
(Note that it must go in various places):
$Everything::HTML::FormObject::AuthorMenu::DB = $mock;
$mock->set_series( 'getNode', 0, 1, 2 );
# genObject() calls skipped in this example...
ok( ! $mock->called( 'getNode' ),
'... should not fetch bound node without one' );
Two things bear more explanation. First, since I don't really know what I want
getNode() to return, I'll give it a dummy series. (I'm pretty sure I'll be
using set_series() on it, because I've done tests like this before. I can't
explain it much beyond intuitive experience.) Second, I'm negating the return
value of called() so it will fit with ok(). This can be a little hard to
see, sometimes.
The last few tests of the first pass all revolve around the textfield() call.
I've already mocked it, so now I'll see whether it was called with the correct
arguments:
my ($method, $args) = $mock->next_call();
shift @$args;
my %args = @$args;
is( $method, 'textfield', '... and should create
a text field' );
is( join(' ', sort keys %args ),
join(' ', sort qw( -name -default -size
-maxlength -override )), '... passing the
essential arguments' );
Several bits here stick out. The next_call() method iterates through
the call stack of mocked methods, in order. It doesn't track every method
called, just the ones you've added through one of Test::MockObject's helper
methods. next_call() returns the name of the method (in scalar context) or
the name of the method an an anonymous array containing the arguments (in list
context).
Since I want to check the arguments passed to textfield(), I call it in list
context. Because the arguments are passed as key => value pairs, the most
natural comparison seems to be as a hash. I use the join-sort idiom quite
often, as I've never quite been comfortable with the list comparison functions
of Test::More. This test would probably be much simpler if I used them.
I explicitly sort both arrays just so a hardcoded list order won't cause unnecessary test failures. (This has bitten me when writing code that ought to work on EBCDIC machines, not just ASCII ones. Of course, if you get Everything up and going on a mainframe, then this is probably the least of your concerns.)
Finally, I test to see whether the returned data is created properly:
is( $result, "some html\nmore html\n",
'... returning the parent object plus the
new textfield html' );
So far, all 13 of the tests succeed. At this point, I started my second
pass through the method, but noticed that I hadn't yet tested that $name
would get its default value from $field. I'll add 'field' to %gpa before
the first pass:
$gpa{f} = 'field';
This test ought to go before the final test in this pass, so I add it there, too. This finishes the first pass:
is( $args{-name}, 'field',
'... and widget name should default
to field name' );
For the second pass, I will test what happens when I provide a node to which to
bind the form object. In an unmocked Everything, this node comes as an
argument to the function. In the tests, I must return them from the mocked
getParamArray(), so that's where I will start. I also set the 'field' value
in the mock object to a sentinel value I'll check for later. Since the value
of $field will be 'field,' it works out nicely. (There's room to be much
more creative on these names, especially if you're trying to sneak the name of
a friend into your software.)
$gpa{bn} = $mock;
$mock->{field} = 'bound node';
Because getNode() has a series set on it and hasn't been called before, it
will return 0. That means that isOfType() won't be called on the author
object, and the default choice won't be modified from its undefined value.
These tests are fairly easy:
genObject( $mock );
($method, $args) = $mock->next_call();
isnt( $method, 'isOfType',
'... not checking bound node type if it is not found' );
shift @$args;
%args = @$args;
is( $args{-default}, undef, '... and not modifying
default selection' );
As before, next_call() comes in handy. Since I already know that
textarea() will be the first (and last, for this pass) method called, I can
make sure that isOfType() was not called.
Two tests follow. One ensures that the code checks the node's type. The other
makes sure that the default value becomes a blank string if the type is
incorrect. To make this work, I had to modify the existing getNode() series
to return two instances of $mock.
$mock->{title} = 'bound title';
$mock->set_series( 'isOfType', 0, 1 );
genObject( $mock );
($method, $args) = $mock->next_call( 2 );
is( $method, 'isOfType', '... if bound node
is found, should check type' );
is( $args->[1], 'user', '... (the user type)' );
($method, $args) = $mock->next_call();
shift @$args;
%args = @$args;
is( $args{-default}, '',
'... setting default to blank string
if it is not a user' );
The only new idea here is of passing an argument to next_call(). I know
getNode() is the first mocked method, so I can safely skip it. These tests
all pass. The final testable condition is where the bound node exists and
is a 'user' type node. The set_series() call in the last test block makes
isOfType() return true for this pass:
genObject( $mock );
($method, $args) = $mock->next_call( 3 );
shift @$args;
%args = @$args;
is( $args{-default}, 'bound title', '... but using
node title if it is' );
I now have 22 successful tests. My original test name plan had 14 tests, but
that number generally grows as I see more logic branches. I could add more
tests, making sure that default values are not overwritten, and that the
essential (hardcoded) attributes of the textfield() are set, but I'm
reasonably confident in the tests as they stand. If something breaks, then I'll add
a test to catch the bug before I fix it, but what's left is simple enough; I
doubt it will break. (Writing that is a good way to have to eat my words
later.)
Testing Another Method (A Less Detailed Example)
One method remains for this form Object: cgiVerify(). When the Engine
processes input from submitted Form Object forms, it must rebuild the objects.
Then, it checks the input against allowed values for the widgets. This method
does just that. Its code is slightly longer, and reads:
my ($this, $query, $name, $USER) = @_;
my $bindNode = $this->getBindNode($query, $name);
my $author = $query->param($name);
my $result = {};
if($author)
{
my $AUTHOR = $DB->getNode($author, 'user');
if($AUTHOR)
{
# We have an author!! Set the CGI param
# so that the
# inherited cgiUpdate() will just do
# what it needs to!
$query->param($name, $$AUTHOR{node_id});
}
else
{
$$result{failed} = "User '$author'
does not exist!";
}
}
if($bindNode)
{
$$result{node} = $bindNode->getId();
$$result{failed} = "You do not have permission"
unless($bindNode->hasAccess($USER, 'w'));
}
return $result;
Rather than describing the writing of the tests (and my steps and missteps therein), I'll just comment on the tests themselves.
my $qmock = Test::MockObject->new();
$mock->set_series( 'getBindNode', 0, 0, 0,
$mock, $mock );
$qmock->set_series( 'param', 0, 'author', 'author' );
Because of the way this method handles things, it's clearer to create another
mock object to pass in as $query. I'm also setting up the two main series
used for the several passes through this method. While writing the tests, I
kept adding new values to these series. This is what remained at the end.
This approach makes more sense to me than setting each mock before each pass,
but it's a matter of style, and either way will work.
$result = cgiVerify( $mock, $qmock, 'boundname' );
($method, $args) = $mock->next_call();
is( $method, 'getBindNode', 'cgiVerify() should get
bound node' );
is( join(' ', @$args), "$mock $qmock boundname",
'... with query object and query
parameter name' );
Here's the reason I used separate mock objects: to tell them apart as arguments
in the getBindNode() call.
($method, $args) = $qmock->next_call();
is( $method, 'param', '... fetching parameter' );
is( $args->[1], 'boundname', '... by name' );
isa_ok( $result, 'HASH', '... and should return a data
structure which' );
The weird test name here is another of my little idioms. isa_ok() adds 'isa
(reference type)' to the end of its test names, and I want them to be as clear
as possible when they're displayed.
$mock->set_series( 'getNode', 0, { node_id =>
'node_id' } );
$result = cgiVerify( $mock, $qmock, 'boundname' );
($method, $args) = $mock->next_call( 2 );
is( $method, 'getNode', '... fetching the node, if an
author is found' );
is( join(' ', @$args), "$mock author user",
'... with the author, for the user type' );
is( $result->{failed}, "User 'author' does not exist!",
'... setting a failure message on failure' );
I like the approach of joining the arguments in a string and doing an is()
or a like() call on them. The benefit of like() is that you can ignore
the $self passed as the first argument, because it's the mock object. I
used is() here to make it more explicit what I'm expecting.
$qmock->clear();
$result = cgiVerify( $mock, $qmock, 'boundname' );
($method, $args) = $qmock->next_call( 2 );
is( $method, 'param', '... setting parameters,
on success' );
is( join(' ', @$args), "$qmock boundname node_id",
'... with the name and node id' );
is( $result->{failed}, undef,
'... and no failure message' );
This bit of code gave me trouble until I added the clear() call. It's worth
remembering that a mock object's stack of mocked calls persists through passes.
I had forgotten that, and was using the first param() call instead of the
second. Oops.
Another thing worth noting is that I pass 'undef' as the expected result to
is(). Conveniently, Test::More silently does the right thing.
$mock->set_always( 'getId', 'id' );
$mock->set_series( 'hasAccess', 0, 1 );
$result = cgiVerify( $mock, $qmock,
'boundname', 'user' );
Here, I exercise the method's final clause. These tests are more complex than the code being tested! Sometimes, it works out that way.
$mock->called_pos_ok( -2 , 'getId',
'... should get bound node id, if it exists' );
is( $result->{node}, 'id',
'... setting it in the resulting node field' );
$mock->called_pos_ok( -1, 'hasAccess',
'... checking node access ');
is( $mock->call_args_string( -1, ' ' ),
"$mock user w",
'... for user with write permission' );
I've moved away from the next_call() approach to the older Test::MockObject
behavior of using positions in the call stack. I'm still not quite pleased
with the names of these methods, but the negative subscripts are handy.
(Maybe I need to add prev_call()). All I have to do is remember that
hasAccess() is called last, and that getId() should be called as the
second-to-last method.
The other new method here is call_args_string(), which simply joins the
arguments at the specified call position together. It saves a bit of typing,
most of which is offset by the long method name.
is( $result->{failed}, 'You do not have permission',
'... setting a failure message if user lacks
write permission' );
$result = cgiVerify( $mock, $qmock, 'boundname', 'user' );
is( $result->{failed}, undef, '... and none if the user
has it' );
These final two tests demonstrate how, at the end of a long series of tests, the available options are whittled down. By the final couple of passes, I'm generally testing only one thing at a time. That always seems mathematically poetic, to me, as if I'm refining with Newton's method.
Conclusion
This whole exercise has produced 39 tests. My next step is to update the test plan with this information.
# way back at the top
use Test::More tests => 39;
This makes it easier to see whether too many or too few tests were run. I get
better results about failures and successes this way, too. As it turns out,
writing the tests for cgiVerify() took about 20 minutes, give or take some
laundry-related distractions. That seems about right for 17 tests on code I
haven't read in several months -- about one a minute, when you know what you're
doing.
It's worth noting the features of this module that lead me to consider mock
objects. Mostly, the process of fetching and building nodes is complex enough
that I didn't really want to hook up a fake database, just so I could go
through all of the code paths required to test that an author is really an
author. If the code had gone through some simple mathematical or textual
manipulations, then I would probably have used black-box testing. Code that relies
on a database abstraction layer (as does Everything) generally makes me reach
for Test::MockObject, however.
For more information on what the module can do, please see its documentation. The current stable version is 0.08, though 0.09 will probably have been released by the time this article is stable.
chromatic is the author of Modern Perl. In his spare time, he has been working on helping novices understand stocks and investing.



