Greg London wrote:
> Evals and typeglobs will let you do it. 
> If you don't like that sort of thing (I don't),
> you can use a module I wrote called SymbolTable
> which hides all the ugliness for you.

Thanks Greg for taking the time to ponder this, but I believe using 
SymbolTable will just provide a nicer syntax for doing this:

     *Module::Under::Test::method2 = sub { ... };

as I previously mentioned. This approach fails to pass this test:

     my $mut1 = Module::Under::Test->new();
     *Module::Under::Test::method2 = sub { ... };
     $mut1->method2(); # modified behavior

     my $mut2 = Module::Under::Test->new();
     $mut2->method2(); # original behavior

As the call to $mut2->method2() will still invoke the modified behavior, 
because the class, rather than the instance, was modified. (But I think 
you got this, as indicated by your second reply.)


> Hm, not a sub{}.
> You'd have to insert a sub that would check the
> reference of the instance and compare it to the one you
> want to bypass. If they're equal, skip. if not equal
> then call method2. something like.....
> 
> my $instance_to_skip = $mut;
> my $intercept_method2 = sub {
>    my $obj=shift(@_);
>    if($obj eq $instance_to_skip) {
>       return;
>   } else {
>      return ($obj->method2(@_)); # ...
>   }
> };
> 
> then assign symboltable for your class so that method2
> is replaced with $intercept_method2.

If I wanted to interject my own layer of indirection at the instance 
level, and was willing to modify the class under test, I'd probably opt 
for an AUTOLOAD method that translated all method calls into calls that 
would execute a sub ref from the object's hash under a key of the same 
name as the method. Then the exact same thing as is possible in 
JavaScript could be done:

$obj->{method2} = sub { ... };

But this is not a trivial change - either from a code complexity or 
performance perspective - to impose on the module under test. If this is 
the only option, then dealing with a bit of syntax ugliness (creating a 
subclass) in the test class is far preferable.

Browsing further on CPAN turns up Class::Unique:
http://search.cpan.org/~friedo/Class-Unique-0.03/lib/Class/Unique.pm

which accomplishes the equivalent with this constructor:

sub new {
     my $class = shift;
     my $obj = { };

     my $unique_class = $class . '::' . refaddr $obj;

     {
         no strict 'refs';
         @{ $unique_class . '::ISA' } = ( $class );
     }

     # so we don't have to rely on ref()
     $obj->{$PKG} = $unique_class;
     return bless $obj, $unique_class;
}

It interjects the address of the object into the class name, then 
creates a subclass using that modified name, and returns an object 
blessed into the subclass. That way each instance has a unique namespace 
in the symbol table. Of course to pull this off your class needs to be a 
subclass of Class::Unique.

If your class under test is properly designed to allow subclassing, you 
should be able to something like:

     my $subclass = __PACKAGE__ . '::test_method1::Module::Under::Test';
     my $mut = Module::Under::Test::new($subclass);
     {
         no strict 'refs';
         @{ $subclass . '::ISA' } = ( 'Module::Under::Test' );
         *{ $subclass . '::method2' } = sub { ... };
     }

But if new() is declared in a superclass, that breaks. And besides, this 
is just creating an uglier, stranger looking subclass. Though it does 
let you get around the limitations of the package statement.

Class::Unique references Class::Prototyped, which simulates a 
prototype-style OO model, as used by JavaScript, in Perl. Schwern's 
Class::Object (incomplete) and Class::Classless are other 
implementations of the same idea. All of these approaches, like 
Class::Unique, require that your code be implemented as a subclass, so 
they aren't ideal for bolting-on to an existing module.


> http://backpan.cpan.org/authors/id/G/GS/GSLONDON/SymbolTable-0.02.readme
> I'm pretty sure it still works.

Is there a reason why you referenced an older version? There seems to be 
a slightly newer version in the current repository:

http://search.cpan.org/~gslondon/Symbol-Table-1.01/Table.pm


> Symbol::Table - An easy interface to symbol tables (no eval(), no *typeglobs )
> 
>   use Symbol::Table;
> 
>   # constructor takes two arguments, 
>   # (1) which TYPE of symbols (PACKAGE,CODE,SCALAR,ARRAY,HASH)
>   # and (2) what package namespace do you wish to examine
>   # (default value for arguments are 'PACKAGE' and current package namespace)
>   # the return value is a symbol table object.
>   my $st_pkg = Symbol::Table->New('PACKAGE', 'main');

So instead of:
     *Module::Under::Test::method2 = sub { ... };

I'd do:
     use Symbol::Table;
     my $st = Symbol::Table->New('CODE', 'Module::Under::Test');
     $st->{method2} = sub { ... };

Not exactly a win from a conciseness perspective.

Where Symbol::Table might prove interesting is in creating a dynamically 
named subclass, though it isn't clear from your man page quite how 
Symbol::Table can be used to create a new package. What does 
Symbol::Table do when the specified namespace doesn't already exist? Do 
you have to create the intervening packages, like:

     my $module = Symbol::Table->New('PACKAGE', 'Module');
     $module->{Under} = Symbol::Table->New('PACKAGE', 'Under');
     $module->{Under}->{Test} = Symbol::Table->New('PACKAGE', 'Test');

Or can you jump directly to:

     my $mut = Symbol::Table->New('CODE', 'Module::Under::Test');
     $mut->{method2} = sub { ... };

I could see creating a subclassing helper module that when given a class 
name string and a list of parent classes, it would create the package, 
set @ISA, and return a reference to a Symbol::Table object that can be 
used to assign methods to. That's probably as close as you can get to a 
dynamically named subclass without using a string eval.

Looks like class_subclass() function in Class::Clone does this:
http://search.cpan.org/~crakrjack/Class-Clone-0.05/lib/Class/Clone.pod

(It makes use of Symbol::Table internally.) However it doesn't return 
anything that would permit easy modification of the generated subclass. 
If you want to override a method, you're on your own.


I wrote:
> A typeglob could be used here:
>      *Module::Under::Test::method2 = sub { ... };
> 
> but that alters the class, rather than the instance, and could impact
> other methods in the unit test. (I suppose one could save the current
> value and restore it later.)

Damian's Hook::LexWrap:
http://search.cpan.org/~dconway/Hook-LexWrap-0.20/lib/Hook/LexWrap.pm

would be yet another way to do this, and with lexically scoped wrappers, 
you wouldn't have to concern yourself with restoring the original 
behavior at the end of the test method.

  -Tom

-- 
Tom Metro
Venture Logic, Newton, MA, USA
"Enterprise solutions through open source."
Professional Profile: http://tmetro.venturelogic.com/
 
_______________________________________________
Boston-pm mailing list
[email protected]
http://mail.pm.org/mailman/listinfo/boston-pm

Reply via email to