Stupid Testing Trick: Inconstant Constants
I have a piece of Perl containing manifest constants that have the same value, but which signify different things. In my testing, I wanted to make sure I was getting the right one out of my code. That seems to mean changing code to test it, which is anathema to me.
It occurred to me today that if I held my tongue right I could change the values of the constants without touching the code they were defined in or exported from. Holy aspect-oriented programming, Batman!
The trick is based on the fact that what use constant
really does is to define a constant subroutine with an empty prototype. Now, Perl will allow me to redefine a subroutine -- grumpily if use warnings qw{redefine}
is in effect, and happily if not. So I thought all I had to do was load the module that exported the constant, hammer its symbol table, and voila!
As an example, assume module Foo
exports manifest constants PLATFORM
and MAGIC
, both having value 42
, but intended for different purposes. To make this trick work, my test needs to:
use Foo (); # NO IMPORTS!{
no warnings qw{ redefine };
sub Foo::PLATFORM () { 9.75 };
sub Foo::MAGIC () { 8 };
}use Foo qw{ PLATFORM MAGIC };
$\ = "\n";
print PLATFORM; # Platform 9-3/4
print MAGIC; # The number between 7 and 9
This works with Perl 5.27.1
, and as far back as 5.6.2
, though under some earlier versions you may need to localize $^W
and set it false. Under all Perls I have tried, running the script under -MO=Deparse
shows that the revised value really is inlined.
I am not sure I am brave enough (or foolish enough) to put something like this in production testing, but for author testing it seems pretty handy.
If you don't want to deal with the ugly redefine warnings yourself, you can use Class::Method::Modifiers to wrap an existing function to return something different:
If you are doing this often for some reason, you could even define a Role::Tiny to apply these modifiers to the package (this would create a new package though, and not affect the original Foo). But definitely still something to do only in tests, and not in production code unless absolutely necessary :)
Thanks for the recommendation. Unfortunately this does not appear to affect the value imported from
Foo
, only the value returned when I callFoo->PLATFORM
. When I replace the block in my sample script with your suggestion, it prints42
twice, though if I call the "manifest constants" as methods I do indeed get the modified value. Of course, I may have misunderstood your suggestion. My modified code is:Maybe I was too brief about what I was trying to do. What I have is a manifest constant defined in
Foo
and imported intoBar
,Baz
, and so on. What I want is to modify the values seen byBar
and friends without modifyingFoo
.You'd need to do the 'around's in a BEGIN{} block for them to take effect before the constants are important from Foo in the 'use' line.
imported*. And you don't need to call them as methods, just accessing Foo::MAGIC should work regardless of importing.
Though on second thought Foo::MAGIC may get compiled out due to being a constant, before the 'around' takes effect if it wasn't done in BEGIN{}.
Enclosing the
around()
call in aBEGIN
block does get me the correct values out. Unfortunately Class::Method::Modifiers does not preserve the prototype of the subroutine, so I get "Prototype mismatch" warnings, and the value is not inlined.The prototype errors arise from the final
eval
ininstall_modifier()
, and would have to be silenced there. What is really needed for this application is forClass::Method::Modifiers
to preserve the prototype.