For Further Exploration
Once you've mastered the basics of Perl testing, you may be interested in more advanced capabilities. For rigorous unit testing of legacy object-oriented applications, Test::MockObject provides a complete, if lengthy, solution. Test::MockObject lets you create objects that mock the behavior of the ones actually used in your application so they present apparently the same interface to the calling tests but under your complete control and without the side effects. (Testing a database is such a common need for mocking that the module DBD::Mock was created to help with it.) This is so-called "white box" testing: instead of looking only at the interfaces a method presents to the outside world, you craft unit tests for it by inspecting its code to see where it calls functions or methods that may alter state. Then you mock those functions to track whether they are being called the way you expect at that point.
If you like to integrate code and its documentation, you might like to do the same with its tests, in which case the CPAN module Test::Inline lets you embed tests within code in an extension of the POD language. Just encapsulate them in POD-like blocks marked "=begin testing" ... "=end testing" and then extract the tests into a test file with the program pod2test that comes with Test::Inline. (This step can be automated in your module's Makefile.PL during the make test phase.)
Test::Unit
If you're coming from a Java/JUnit background and want a familiar testing environment in Perl, the CPAN module Test::Unit is for you. You define set_up() and tear_down() procedures to prepare for and clean up after each test case so there can be no side effects between tests. Test::Unit even provides the green/red bar, courtesy of PerlTk.
Coverage Testing
The value of tests is proportional to the proportion of possible equivalence classes that are exercised. Generating the valid equivalence classes is still a manual task, but you can at least verify that all the statements in your code have been exercised during testing. Once again, this has been automated. Modules for measuring code coverage have existed for some time: Devel::DProf and Devel::SmallProf profile code and the parts that haven't been called can be inferred. Devel::Coverage shows them explicitly. But that's still too much work for the lazy. Therefore the modern module Devel::Cover has been extended to include coverage for tests. Now, simply by defining an environment variable, you can trigger analysis during make test and generate a coverage database that can be analyzed with the cover program that comes with Devel::Cover. That program outputs HTML that provides a tabular representation of the lines of code that did not get exercised. There's even another module, Test::Pod::Coverage, that verifies that all the files in your distribution provide documentation.
Legacy Application Testing
Perl programmers are often faced with having to deal with legacy applications which lack unit tests or, for that matter, coherent units. Perl's testing framework at this point becomes useful for creating end-to-end regression tests to provide a safety net while the application is modified. This can even be extended to testing web interfaces via the CPAN module Test::WWW::Mechanize. If you find the work of programming the necessary inputs to navigate a web interface too tedious, the module HTTP::Recorder will do it for you.
Automated tests can be useful for other purposes than just application testing. With the web-based testing tools just mentioned, you could write something like Listing Four to verify that your bank account is doing alright. (If you want to put it in a cron job, knowing that the exit code of a test run is the number of tests that failed may be useful.)
use strict; use warnings; use Test::WWW::Mechanize tests => 7; my $mech = Test::WWW::Mechanize->new; $mech->get_ok("http://www.mybank.example.com/", "My bank is still there"); $mech->content_contains("Login", "and it still has a login page"); my ($username, $password) = @ARGV; $mech->set_visible($username, $password); ok($mech->submit->is_success, "Login form submitted okay"); $mech->content_like(qr/Account data/, "Logged in okay"); my ($balance) = $mech->content =~ /Checking: \$([\d.]+)/; ok(defined($balance), "Found checking account balance"); ok($balance > 1000, "Got enough for now");