in reply to Re: Re: Documenting Methods/Subs
in thread Documenting Methods/Subs

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.

Replies are listed 'Best First'.
Test::Class & self-shunt pattern
by adrianh (Chancellor) on Jan 12, 2003 at 23:07 UTC

    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.

      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...)

        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.

        Fair point. Although in some circumstances that could be what you want :-)

        Instead of replacing stuff I sometimes just add instrumentation with something like Hook::LexWrap - handy when retrofitting tests onto tightly coupled code.

        This really comes down to how much value you see in the strict typing of variables.

        Well, all the ways of doing it in perl suck in one way or another so I don't often use it, but it's nice if done right (Eiffel springs to mind).

        Method selecters are cool, and interfaces would be yet another solution.

        Maybe in perl6 :-)

Re: Re: Re: Re: Documenting Methods/Subs
by autarch (Hermit) on Jan 16, 2003 at 03:20 UTC

    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, ...

    my %p = validate( @_, { bar => { can => 'do_that_thang' } } );
      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.

        I totally agree with the assertion that well factored code needs little in the need of code documentation. However the fact that perl lacks the ability to talk succinctly about type means that some things are less obvious than they are in other languages unless you add explicit assertions.

        Consider the example of specifying a logger:

        sub new { my $class = shift; my %self = validate(@_, {logger => { isa => 'Logger' } } ); return( bless \%self, $class ); };

        The Logger object is not used by new(). It may not be used anywhere near the constructor code - especially if in a subclass. Without the assertion it's not obvious what kind of thing the logger should be.

        If I passed something that wasn't a Logger the problem wouldn't show up until it was used - potentially quite a distance from the incorrect call to new() making debugging harder.

        Writing a test that concisely states "this has to be a logger object in all subclasses" is pretty much impossible.

        Writing a comment has the usual problem of getting out of sync with the code, and doesn't solve the debugging issues.

        Having an assertion (using validate or something like Carp::Assert::More seems a better solution.