A Test::MockObject Illustrated Example
by chromaticJuly 10, 2002
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.
|
Related Reading
Perl in a Nutshell, 2nd Edition |
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.






