Re: testing with stubs
On Thu, Dec 12, 2002 at 03:27:32PM -0600, Danny Faught wrote: I have a legacy Perl script, not object-oriented but at least ported to Perl 5. I want to use a real-world example, rather than new code like what Kent Beck uses in the book Test-Driven Development. So I thought I'd implement a unit test for one of the functions in this old script. I'm using Test::Unit::Procedural. I'm pulling my hair out trying to stub out the functions that the function under test is calling. Here's the function (complete with an error that will be corrected as part of the exercise): sub abort_handler { my ($event) = @_; print STDERR stress_driver: aborted by SIG$event-data()\n; log (stress_driver: aborted by SIG$event-data()); exit (cleanup); } I want to stub out (replace) the log and cleanup functions and the builtin exit function. In your test, do something like this: BEGIN { # Override with itself so it can be overridden at run-time later. *CORE::GLOBAL::exit = sub { CORE::exit @_; } } { my $exit_code; no warnings 'redefine'; local *CORE::GLOBAL::exit = sub { $exit_code = shift; goto EXIT; }; my @logs = (); local *Foo::log = sub { push @logs, @_; } my $cleanup_called = 0; local *Foo::cleanup = sub { $cleanup_called++ return something; } ...do code which calls abort handler... EXIT: ..tests here... } I'll probably also want to redirect STDERR to capture that output as well That's easy, tie it. Look at t/lib/TieOut.pm in the Test-Simple tarball. But I can't figure out how to turn unit test mode on and off. Scripts are hard to test. Libraries are easy. So... Step 1: pull all the subroutines out of the script and into a seperate library. Step 2: test that library. That leaves a lot less to try and test in the script. Beyond that the real problem is that scripts must be called as seperate processes which makes much of the subroutine overriding tricks above difficult. I have a trick which simulates running a perl script, but all its really doing is eval'ing the code in the current process. This means the tricks above will work. It can be found here: http://www.pobox.com/~schwern/tmp/esmith-TestUtils.pm Adapt as you like. -- Michael G. Schwern [EMAIL PROTECTED]http://www.pobox.com/~schwern/ Perl Quality Assurance [EMAIL PROTECTED] Kwalitee Is Job One conway: unit of mind expansion. One Conway == ~20 lines of Perl code found in $CPAN/authors/id/D/DC/DCONWAY, which gives the sensation of your brain being wrapped around a brick, with kiwi juice squeezed on top. -- Ziggy
testing with stubs
I'm writing an article about test-first programming for Linux Magazine. Unfortunately, my forte is more with black-box testing. Could I get some suggestions about implementation? I have a legacy Perl script, not object-oriented but at least ported to Perl 5. I want to use a real-world example, rather than new code like what Kent Beck uses in the book Test-Driven Development. So I thought I'd implement a unit test for one of the functions in this old script. I'm using Test::Unit::Procedural. I'm pulling my hair out trying to stub out the functions that the function under test is calling. Here's the function (complete with an error that will be corrected as part of the exercise): sub abort_handler { my ($event) = @_; print STDERR stress_driver: aborted by SIG$event-data()\n; log (stress_driver: aborted by SIG$event-data()); exit (cleanup); } I want to stub out (replace) the log and cleanup functions and the builtin exit function. I'll probably also want to redirect STDERR to capture that output as well, or more likely I should refactor that line into a different log subroutine. Of course, I need to set up the test harness with minimal or no modifications to the code under test. I can use use subs and the exporter to override the exit call. And I can use an ugly typeglob hack to override the user-defined subroutines. But I can't figure out how to turn unit test mode on and off. The only way I can get the override of exit() to work is to use sd_stubs to pull it in with the exporter. Here's the code that I added to the top of the main program that tries to do that: if ($ARGV[0] $ARGV[0] eq -t) { use sd_stubs; require sd_unit_test; } If I try to run the program without -t, so it functions as it did before, my exit stub is still in place. This code fixes that problem (after moving exit from @EXPORT to @EXPORT_OK): BEGIN { if ($ARGV[0] $ARGV[0] eq -t) { my @overrides = qw(exit); use sd_stubs @overrides; require sd_unit_test; } } But now the sd_unit_test package can't see the main::abort_handler function. I'm lost in a mess of namespaces at this point. Can someone point out in what way I'm being boneheaded here, and/or what the most reasonable way to stub out these functions would be? Here's sd_stubs.pm and sd_unit_test.pm. I split them into two files to solve a different problem I ran across. - package sd_stubs; require Exporter; @ISA = qw(Exporter); @EXPORT = qw(exit); use warnings; use strict; use subs qw(exit); sub log_stub { print LOG_STUB\n; return 0; } sub exit { print EXIT_STUB\n; return 0; } 1; package sd_unit_test; use warnings; use strict; use Test::Unit::Procedural; my $log_main = \log; { no warnings; *main::log = \sd_stubs::log_stub; } sub test_abort1 { assert (main::abort_handler() == 0); } create_suite(); CORE::exit(run_suite()); 1; -- Danny Faught Tejas Software Consulting http://www.tejasconsulting.com