An Introduction to Test::MockDBI
by Mark Leighton Fisher
|
Pages: 1, 2, 3
DBI Return Values
Test::MockDBI defaults to returning a success (true) value for all DBI
method calls. This fits well with the usual techniques of DBI programming,
where the first DBI error causes the program to stop what it is doing.
Test::MockDBI's bad_method() method creates a rule that forces a
failure return value on the specified DBI method when the current DBI testing
type and SQL match those of the rule. Arbitrary DBI method return value
failures like these are difficult (at best) to generate with a test database.
Test::MockDBI's set_retval_scalar() and
set_retval_array() methods create rules for what database values
to return. Set rules for scalar return values (arrayrefs and hashrefs) with
set_retval_scalar() and for array return value rules with
set_retval_array(). You can supply a value to be returned every
time the rule matches, which is good when extracting single rows out of the
database, such as configuration parameters. Alternatively, pass a CODEREF that
will be called each time the rule fires to return a new value. Commonly, with
SELECT statements, the DBI returns one or more rows, then returns an empty row
to signify the end of the data. A CODEREF can incorporate a state machine that
implements this "return 1+ rows, then a terminator" behavior quite easily.
Having individual state machines for each rule is much easier to develop with than
having one master state machine embedded into Test::MockDBI's core. (An early
alpha of Test::MockDBI used the master state machine approach, so I have
empirical evidence of this result--I am not emptily theorizing here.)
Depending on what tools you have for creating your test databases, it may be difficult to populate the test database with all of the values you need to test against. Although it is probably not so much the case today, only a few years ago populating a database with Unicode was difficult, given the national-charset-based tools of the day. Even today, a document management system might be difficult to populate with weird file types. Test::MockDBI makes these kinds of tests much easier to carry out, as you directly specify the data for the mock database to return rather than using a separate test database.
This ease of database value testing also applies when you need to test against combinations of database values that are unlikely to occur in practice (the old "comparing apples to battleships" problem). If you need to handle database value corruption--as in network problems causing the return of partial values from a Chinese database when the program is in the U.S.--this ability to completely specify the database return values could be invaluable in testing. Test::MockDBI lets you take complete control of your database return values without separating test code and test data.
Simplicity: Test::MockDBI's Standard-Output-Based Interface
This modern incarnation of the age-old stubbed-functions technique also uses
the old technique of "printf() and scratch head" as its output
interface. This being Perl we are working with, and not FORTRAN IV (thank
goodness), we have multiple options beyond the use of unvarnished standard
output.
One option that I think integrates well with DBI-using module testing is to redirect standard output into a string using IO::String. You can then match the string against the regex you are looking for. As you have already guessed, use of pure standard output integrates well with command-line program testing.
What you will look for, irrespective of where your code actually looks, is the output of each DBI method as it executes--the method name and arguments--along with anything else your code writes to standard output.
Bind Test Data to Test Code
Because DBI and database return values are bound to your test programs when using Test::MockDBI, there is less risk of test data getting out of sync with the test code. A separate test database introduces another point of failure in your testing process. Multiple test databases add yet another point of failure for each database. Whatever you use to generate the test databases also introduces another point of failure for each database. I can imagine cases where special-purpose programs for generating test databases might create multiple points of failure, especially if the programs have to integrate data from multiple sources to generate the test data (such as a VMS Bill of Materials database and a Solaris PCB CAD file for a test database generation program running on Linux).
One of the major advances in software engineering is the increasing ability to gather and control related information together--the 1990s advance of object-oriented programming in common languages is a testimony to this, from which we Perl programmers reap the benefits in our use of CPAN. For many testing purposes, there is no need for separate test databases. Without that need for a separate test database, separating test data from test code only complicates the testing process. Test::MockDBI lets you bind together your test code and test data into one nice, neat package. Binding is even closer than code and comments, as comments can get out of sync with their code, while the test code and test data for Test::MockDBI cannot get out of sync too far without causing their tests to fail unexpectedly.
When to Use Test::MockDBI
DBI's trace(), DBD::Mock, and
Test::MockDBI are complementary solutions to the problem of testing DBI
software. DBI's trace() is a pure tracing mechanism, as it does
not change the data returned from the database or the DBI method return values.
DBD::Mock works at level of a database driver, so you have to look at your DBI
testing from the driver's point of view, rather than the DBI caller's point of
view. DBD::Mock also requires that your code supports configurable DBI DSNs,
which may not be the case in all circumstances, especially when you must
maintain or enhance legacy DBI software.
Test::MockDBI works at the DBI caller's level, which is (IMHO) more natural for testing DBI-using software (possibly a matter of taste: TMTOWTDI). Test::MockDBI's interface with your DBI software is a set of easy-to-program, regex-based rules, which incorporate a lot of power into one or a few lines of code, thereby using Perl's built-in regex support to best advantage. This binds test data and test code tightly together, reducing the chance of synchronization problems between the test data and the test code. Using Test::MockDBI does not require modifying the current code of the DBI software being tested, as you only need additional code to enable Test::MockDBI-driven DBI testing.
Test::MockDBI takes additional coding effort when you need to test DBI program performance. It may be that for performance testing, you want to use test databases rather than Test::MockDBI. If you were in any danger of your copy of DBI.pm becoming corrupted, I don't know whether you could adequately test that condition with Test::MockDBI, depending on the corruption. You would probably have to create a special mock DBI to test corrupted DBI code handling, though you could start building the special mock DBI by inheriting from Test::MockDBI without any problems from Test::MockDBI's design, as it should be inheritance-friendly.

