I'm not actually that much of a fan of enforcing object types in method signatures to be honest because it can lead to a certain amount of pain at testing time, especially if you're using the very lovely and worthwhile Test::Class. The 'Self Shunt' testing pattern is remarkably handy, that's when you do:
sub test_the_thing : Test {
my $self = shift;
...
$object_under_test->do_the_thing( foo => $self, ... )
...
}
sub called_from_do_the_thing {
my $self = shift;
ok array_eq(\@_, [qw/foo bar baz/];
}
and similar tricks. If you're enforcing the object type you have to draft in Test::MockObject, which is great, but is also substantially more complex.
| [reply] [d/l] |
If you're enforcing the object type you have to draft
in Test::MockObject, which is great, but is also substantially more
complex.
You can still use
the self-shunt unit testing pattern with enforced object types and
Test::Class if you take advantage of perl's run-time method
dispatch.
For example, consider a class that takes an optional logging object
to allow custom logging on an object by object basis.
| [reply] [d/l] [select] |
Cute trick. It's actually something I'd considered but rejected (but I would say that wouldn't I?), but your wrapper is very neat.
The problem with it, as I see it is that, with optimisitic typing (ie, just passing in $self), you get an immediate failure if the object under test calls an unexpected method simply because you haven't implemented it. With a localized @ISA, Logger's methods becomes callable and an unexpected method call could get dispatched to real code (and you can't be sure that that would throw an exception.
If Params::Validate does the right thing ($obj->isa('foo'), not UNIVERSAL::isa($obj, 'foo')) then you could always do
sub _do_as (&$) {
my($block, $fake_class) = @_;
local *Object::Test::isa = sub {
my($self, $target_class) = @_;
return $target_class eq $fake_class ||
$self->SUPER::isa($target_class);
};
$block->();
}
If the validater calls UNIVERSAL::isa, you could override that instead, but it would be rather more awkward. And it's all a good deal more awkward than just chucking the test object in without adornment.
This really comes down to how much value you see in the strict typing of variables. Personally I am unconvinced by it. I'd prefer to see a good test suite (and Smalltalk style method selectors, but they're rather harder to come by in Perl. Smalltalk method selectors are great though. They give you lots more naming opportunities, your 'new' method would become
newWithLogger: aLogStream
^ self new setLogger: aLogStream
which isn't necessarily the most convincing of arguments, but believe me it's great with more complex method signatures...) | [reply] [d/l] [select] |
my %p = validate( @_, { bar => { can => 'do_that_thang' } } );
| [reply] [d/l] |
That's certainly a possibility. However (and I'm aware that this may sound like me attempting to move the goalposts), in well factored code the method is likely to be short enough that anyone reading the code will be able to see at a glance that bar will be expected to do_that_thang so the validator declaration is unlikely to introduce any real clarification.
Actually, my whole argument for not having much in the way of code documentation is predicated on the belief that well factored code doesn't need much in the way of code documentation and, more strongly, that code that does need substantial explanatory documentation probably isn't well factored enough.
| [reply] [d/l] [select] |