tags:

views:

74

answers:

2

There is a Perl script in our environment that I now need to maintain. It is full of bad practices, including using (and re-using) global variables throughout the script. Before I start making changes to the script, I was going to try to write some test scripts so I can have a good regression base. To do this, I was going to use a method described on this page.

I was starting by writing tests for a single subroutine. I put this line somewhat near the top of the script I am testing:

return 1 if ( caller() );

That way, in my test script, I can

require 'script_to_test.pl';

and it won't execute the whole script.

The first subroutine I was going to test makes a lot of use of global variables that are set throughout the script. My thought was to try to override these variables in my test script, something like this:

require_ok('script_to_test.pl');
$var_from_other_script = 'Override Value';
ok( sub_from_other_script() );

Unfortunately (for me), the script I am testing has a massive "my" block at the top, where it declares all variables used in the script. This prevents my test script from seeing/changing the variables in the script I'm running tests against.

I've played with Exporter, Test::Mock..., and some other modules, but it looks like if I want to be able to change any variables I am going to have to modify the other script in some fashion.

My goal is to not change the other script, but to get some good tests running so when I do start changing the other script, I can make sure I didn't break anything. The script is about 10,000 lines (3,000 of them in the main block), so I'm afraid that if I start changing things, I will affect other parts of the code, so having a good test suite would help.

Is this possible? Can a calling script modify variables in another script declared with "my"?


And please don't jump in with answers like, "Just re-write the script from scratch", etc. That may be the best solution, but it doesn't answer my question, and we don't have the time/resources for a re-write.

+2  A: 

If the script has a package declaration (or if you can add one without changing the behavior of the script), then you can change the my declaration to an our declaration, and change variables using the fully qualified variable name.

Old script:

my($a,@b,$c,%d);

Change to:

package Some::Package;
our($a,@b,$c,%d);

And in your test script:

sub_from_other_script();
$Some::Package::c = 42;
$Some::Package::d{$key} = $value;
sub_from_other_script();
mobrule
If the old script is a standalone script, it is always safe to add a package name and to use `our` to scope the variables.
mobrule
The script does not have a package declaration. I might try adding one though...
BrianH
Without the package declaration (but still changing `my` --> `our`), I suspect that you could just manipulate `$main::var_from_other_script` from the test script.
mobrule
Adding a package declaration is a good first step on the (long) road to refactoring this module. Once you start namespacing the code, you can start dividing one huge script into several slightly smaller scripts in their own namespaces, and which should start to become real modules.
Ether
+4  A: 

If you want to keep the variables lexical (if there are closures built with them) you can use the module PadWalker to poke around.

include something like this in the old code:

package somepackage;

use PadWalker qw/peek_my/;

my $x = 1;
# big my block declaration...

our $lexpad = peek_my 0;

then in your test code:

${ $somepackage::lexpad->{'$x'} } = 2;
Eric Strom
This works perfectly, with minimal changes to the original script.
BrianH