More Lightning Articles
by Mark Leighton Fisher, chromatic, Shlomi Fish, Bob DuCharme
|
Pages: 1, 2, 3
Debug Your Programs with Devel::LineTrace
by Shlomi Fish
Often, programmers find a need to use print statements to output information to the screen, in order to help them analyze what went wrong in running the script. However, including these statements verbatim in the script is not such a good idea. If not promptly removed, these statements can have all kinds of side-effects: slowing down the script, destroying the correct format of its output (possibly ruining test-cases), littering the code, and confusing the user. It would be a better idea not to place them within the code in the first place. How, though, can you debug without debugging?
Enter Devel::LineTrace, a Perl module that can assign portions of code to execute at arbitrary lines within the code. That way, the programmer can add print statements in relevant places in the code without harming the program's integrity.
Verifying That use lib Has Taken Effect
One example I recently encountered was that I wanted to use a module I wrote
from the specialized directory where I placed it, while it was already installed
in the Perl's global include path. I used a use lib "./MyPath"
directive to make sure this was the case, but now had a problem. What if there
was a typo in the path of the use lib directive, and as a result,
Perl loaded the module from the global path instead? I needed a way to verify
it.
To demonstrate how Devel::LineTrace can do just that, consider
a similar script that tries to use a module named CGI from the
path ./MyModules instead of the global Perl path. (It is a bad idea to
name your modules after names of modules from CPAN or from the Perl
distribution, but this is just for the sake of the demonstration.)
#!/usr/bin/perl -w
use strict;
use lib "./MyModules";
use CGI;
my $q = CGI->new();
print $q->header();
Name this script good.pl. To test that Perl loaded the CGI module
from the ./MyModules directory, direct Devel::LineTrace to print the
relevant entry from the %INC internal variable, at the first line
after the use CGI one.
To do so, prepare this file and call it test-good.txt:
good.pl:8
print STDERR "\$INC{CGI.pm} == ", $INC{"CGI.pm"}, "\n";
Place the file and the line number at which the trace should be inserted on the first line. Then comes the code to evaluate, indented from the start of the line. After the first trace, you can put other traces, by starting the line with the filename and line number, and putting the code in the following (indented) lines. This example is simple enough not to need that though.
After you have prepared test-good.txt, run the script
through Devel::LineTrace by executing the following command:
$ PERL5DB_LT="test-good.txt" perl -d:LineTrace good.pl
(This assumes a Bourne-shell derivative.). The PERL5DB_LT
environment variable contains the path of the file to use for debugging, and
the -d:LineTrace directive to Perl instructs it to debug the
script through the Devel::LineTrace package.
As a result, you should see either the following output to standard error:
$INC{CGI.pm} == MyModules/CGI.pm
meaning that Perl indeed loaded the module from the MyModules sub-directory of the current directory. Otherwise, you'll see something like:
$INC{CGI.pm} == /usr/lib/perl5/vendor_perl/5.8.4/CGI.pm
...which means that it came from the global path and something went wrong.
Limitations of Devel::LineTrace
Devel::LineTrace has two limitations:
- Because it uses the Perl debugger interface and stops at every line (to check whether it contains a trace), program execution is considerably slower when the program is being run under it.
- It assigns traces to line numbers, and therefore you must update it if the line numbering of the file changes.
Nevertheless, it is a good solution for keeping those pesky
print statements out of your programs. Happy LineTracing!
Using Test::MockDBI
by Mark Leighton Fisher
What if you could test your program's use of the DBI just by creating a set of rules to guide the DBI's behavior—without touching a database (unless you want to)? That is the promise of Test::MockDBI, which by mocking-up the entire DBI API gives you unprecedented control over every aspect of the DBI's interface with your program.
Test::MockDBI uses Test::MockObject::Extends
to mock all of the DBI transparently. The rest of the program knows nothing
about using Test::MockDBI, making Test::MockDBI ideal for testing programs that
you are taking over, because you only need to add the Test::MockDBI invocation code—
you do not have to modify any of the other program code. (I have found this
very handy as a consultant, as I often work on other people's code.)
Rules are invoked when the current SQL matches the rule's SQL pattern. For
finer control, there is an optional numeric DBI testing type for each rule, so
that a rule only fires when the SQL matches and the current DBI
testing type is the specified DBI testing type. You can specify this numeric
DBI testing type (a simple integer matching /^\d+$/) from the
command line or through Test::MockDBI::set_dbi_test_type(). You
can also set up rules to fail a transaction if a specific
DBI::bind_param() parameter is a specific value. This means there
are three types of conditions for Test::MockDBI rules:
- The current SQL
- The current DBI testing type
- The current
bind_param()parameter values
Under Test::MockDBI, fetch*() and select*()
methods default to returning nothing (the empty array, the empty hash, or undef
for scalars). Test::MockDBIM lets you take control of their returned data with
the methods set_retval_scalar() and
set_retval_array(). You can specify the returned data directly in
the set_retval_*() call, or pass a CODEREF that generates a return
value to use for each call to the matching fetch*() or
select*() method. CODEREFs let you both simulate DBI's
interaction with the database more accurately (as you can return a few rows,
then stop), and add in any kind of state machine or other processing
needed to precisely test your code.
When you need to test that your code handles database or DBI failures,
bad_method() is your friend. It can fail any DBI method, with the
failures dependent on the current SQL and (optionally) the current DBI testing
type. This capability is necessary to test code that handles bad database
UPDATEs, INSERTs, or DELETEs, along with
being handy for testing failing SELECTs.
Test::MockDBI extends your testing capabilities to testing code that is
difficult or impossible to test on a live, working database. Test::MockDBI's
mock-up of the entire DBI API lets you add Test::MockDBI to your programs
without having to modify their current DBI code. Although it is not finished
(not all of the DBI is mocked-up yet), Test::MockDBI is already a powerful tool
for testing DBI programs.

