An Introduction to Test::MockDBI
by Mark Leighton Fisher
July 21, 2005
Prelude
How do you test DBI programs:
- Without having to modify your current program code or environment
settings?
- Without having to set up multiple test databases?
- Without separating your test data from your test code?
- With tests for every bizarre value your program will ever have to
face?
- With complete control over all database return values, along with all DBI
method return values?
- With an easy, regex-based rules interface?
You test with Test::MockDBI, that's
how. Test::MockDBI provides all of this by using Test::MockObject::Extends to
mock up the entire DBI API. Without a solution like Test::MockDBI--a
solution that enables direct manipulation of the DBI--you'll have to trace
DBI methods through a series of test databases.
You can make test databases work, but:
- You'll need multiple (perhaps many) databases when you need multiple sets
of mutually inconsistent values for complete test coverage.
- Some DBI failure modes are impossible to generate through any test
database.
- Depending on the database toolset available, it may be difficult to insert
all necessary test values--for example, Unicode values in ASCII applications,
or bizarre file types in a document-manager application.
- Test databases, by definition, are separate from their corresponding test
code. This increases the chance that the test code and the test data will fall
out of sync with each other.
Using Test::MockDBI avoids these problems. Read on to learn how
Test::MockDBI eases the job of testing DBI applications.
A Mock Up of the Entire DBI
Test::MockDBI mocks up the entire DBI API by using Test::MockObject::Extends
to substitute a Test::MockObject::Extends object in place of the DBI. A feature of this approach
is that if the DBI API changes (and you use that change), you will notice
during testing if you haven't upgraded Test::MockDBI, as your program will
complain about missing DBI API method(s).
Mocking up the entire DBI means that you can add the DBI testing code into
an existing application without changing the initial application code--using
Test::MockDBI is entirely transparent to the rest of your application, as it
neither knows nor cares that it's using Test::MockDBI in place of the DBI.
This property of transparency is what drove me to develop Test::MockDBI, as it
meant I could add the Test::MockDBI DBI testing code to existing client
applications without modifying the existing code (handy, for us
consultants).
Further enhancing Test::MockDBI's transparency is the DBI testing type
class value. Testing is only enabled when the DBI testing type is non-zero, so
you can just leave the DBI testing code additions in your production code--users will not even know about your DBI testing code unless you tell them.
Mocking up the entire DBI also means that you have complete control of the
DBI's behavior during testing. Often, you can simulate a SELECT
DBI transaction with a simple state machine that returns just a few rows from
the (mocked up) database. Test::MockDBI lets you use a CODEREF to supply
database return values, so you can easily put a simple state machine into the
CODEREF to supply the necessary database values for testing. You could even put
a delay loop into the CODEREF when you need to perform speed tests on your
code.
Rules-Based DBI Testing
You control the mocked-up DBI of Test::MockDBI with one or more rules that
you insert as Test::MockDBI method calls into your program. The default DBI
method values provided by Test::MockDBI make the database appear to have a hole
in the bottom of it--all method calls return OK, but you can't get any data
out of the database. Rules for DBI methods that return database values (the
fetch*() and select*() methods) can use either a
value that they return directly for matching method calls, or a CODEREF called
to provide a value each time that rule fires. A rule matches when its DBI
testing type is the current testing type and the current SQL matches the rule's
regular expression. Rules fire in the order in which you declare them, so
usually you want to order your rules from most-specific to least-specific.
The DBI testing type is an unsigned integer matching /^d+$/.
When the DBI testing type is zero, there will be no DBI testing (or at least, no
mocked-up DBI testing) performed, and the program will use the DBI normally. A
zero DBI testing type value in a rule means the rule could fire for any
non-zero DBI testing type value--that is, zero is the wildcard DBI testing
type value for rules. Set the DBI testing type either by a first command-line
argument of the form:
--dbitest[=DTT]
where the optional DTT is the DBI testing type (defaulting to
one), or through Test::MockDBI's set_dbi_test_type() method.
Setting the DBI testing type through a first command-line argument has the
advantage of requiring no modifications to the code under test, as this
command-line processing is done so early (during BEGIN time for
Test::MockDBI) that the code under test should be ignorant of whether this
processing ever happened.
[1] [2] [3] Next