Ovid has asked for the wisdom of the Perl Monks concerning the following question:

This is more of an idle thought than a serious need, but in trying to clean up a test suite, I realized that I had a bit of a problem. In this particular test suite, the modules are loaded and remain memory-resident when moving from test class to test class (xunit style testing but with hand-rolled test software.) As a result, if I alter the behavior of any actual code, I must be sure to restore that behavior when I'm done lest other test classes use the altered behavior and fail mysteriously. More than once we have a test class pass when run by itself but fail when run with another class. This is part of the obstacle that I face.

One of the problems with the test suite is that it frequently outputs a lot of data that's not part of the test output. Warnings get spit out, log messages are written to STDOUT and STDERR, etc. When a test suite takes over an hour to run, no one's going to sit there and read all of those, so I'm capturing them and testing their output. For one class, here's what I've done (setup and teardown methods are called at the beginning and end of each test in the class):

my $LOG_ERROR; + my $_log_error_sub_cache; sub setup { my $class = shift; $class->SUPER::setup_test; $_log_error_sub_cache = *RTK::Digitrak::LogRun::_log_error{CODE}; no warnings 'redefine'; *Foo::Bar_log_error = sub($) { $LOG_ERROR = shift }; } + sub teardown { my $class = shift; $class->SUPER::teardown_test; no warnings 'redefine'; *Foo::Bar:_log_error = $_log_error_sub_cache; }

In my individual tests, I can now test that the $LOG_ERROR has the error message that I expect. I have to restore the original sub in the teardown or else the next test class that's run will have the altered behavior and this is bad because it will hide the data that I need to test.

What I would like to do is something like this:

use Foo::Test::LogMessages; my $log = Foo::Test::LogMessages->new; sub TEST_WHICH_HAS_LOG_OUTPUT { # do something $log->capture_log_only_for_this_scope; is($log->message, qw/some message/, '... and we should have an error +'); } sub TEST_WHICH_DOES_NOT_HAVE_LOG_OUTPUT { # do something }

The idea here is that I only want the side effect of suppressing the log message in particular subs. I could put a $log->end_capture_log or something similar at the end of the test sub but if a programmer forgets to do that, we could have plenty of log messages get suppressed and not even realize it. If somehow the log capturing routine could recognize the scope that it's in and disable itself when it's done, that would be handy. This would sort of be like local *Foo::Bar = sub { ... };, but without wanting to deal with all of the explicit typing or the typos inherent in dealing with full package names.

Cheers,
Ovid

New address of my CGI Course.

Replies are listed 'Best First'.
Re: "local" side effects
by Somni (Friar) on May 21, 2004 at 17:42 UTC

    The canonical way of responding to scope at the Perl level (without $^H chicanery) is to return an object that you let fall out of scope at the appropriate time.

    So, for example, you'd use something like:

    sub TEST_WHICH_HAS_LOG_OUTPUT { my $dummy = $log->capture_log_only_for_this_scope; is(...); }

    Have you considered doing something along those lines?

      I have. It's probably the right approach for Test::MockPackage, though I waffle back and forth on taking a block as an argument as Test::Exception's dies_ok() does.

      Duh. Some days I have a brain cramp. That eluded me at first because I don't want a lexically scoped variable, but obviously using that to trigger how the side effect is enabled and destroyed is an easy thing to do. Silly me.

      Cheers,
      Ovid

      New address of my CGI Course.