Discussion:
RFC: Devel::Mockable and Devel::Mock::Generic::InterfaceTester
David Cantrell
2012-10-25 15:05:05 UTC
Permalink
[CCing chromatic in case he's not on perl-qa; my apologies if you get
two copies]

I'm liberating some code that we use at work. A first cut at CPAN-
ising it is here (yes, I know it has no tests yet):
https://github.com/DrHyde/perl-Devel-Mockable

What do people think? The aim is to:
* make it easy to make your code's interfaces to other code
mockable;
* test that your code calls other code correctly, without
all the overhead of actually calling that other code

I don't think it's really doing the same job as Test::MockObject.
That's more for mocking things that might feed data *into* your code,
whereas mine is aimed more at testing how you call those other things in
the first place. Also I think that my code's interface is nicer :-)
--
David Cantrell | A machine for turning tea into grumpiness

All praise the Sun God
For He is a Fun God
Ra Ra Ra!
Karen Etheridge
2012-10-26 22:29:27 UTC
Permalink
Post by David Cantrell
I'm liberating some code that we use at work. A first cut at CPAN-
https://github.com/DrHyde/perl-Devel-Mockable
I just took a quick glance while I had a minute, so this isn't very
in-depth:

- our::way: is that a local pragma? it isn't on cpan.
- instead of manually writing into the symbol table, use one of the export
modules: Exporter, Sub::Exporter, Exporter::Declare etc.
- I'm not sure if this belongs in the Devel namespace - someone else with
better knowledge of the nomenclature would be better able to say.

How does this add value above simply storing a class name in an accessor
(e.g. a Class::Accessor field or Moose attribute)? In moose, I could do
this:

has useragent => ( is => 'ro', isa => 'Str', default => 'LWP::UserAgent' );

and then in testing, simply construct my class as:
my $obj = Foo->new(..., useragent => 'Test::LWP::UserAgent');
...to swap out its implementation with something that is mockable.
--
Don't anthromoporphize computers. They _hate_ that!
. . . . .
Karen Etheridge, ***@etheridge.ca GCS C+++$ USL+++$ P+++$ w--- M++
David Cantrell
2012-10-26 23:01:19 UTC
Permalink
Post by Karen Etheridge
Post by David Cantrell
I'm liberating some code that we use at work. A first cut at CPAN-
https://github.com/DrHyde/perl-Devel-Mockable
What do people think?
I just took a quick glance while I had a minute, so this isn't very
- our::way: is that a local pragma? it isn't on cpan.
Ah, good catch. That's our internal shortcut at work for
use strict;
use warnings;
bunch of other stuff

I'll kill that right now.

But for anyone else paying attention, I'm not asking for code review.
I'll write tests that will catch silly errors like that. I'm interested
in the name and the concept.
Post by Karen Etheridge
- instead of manually writing into the symbol table, use one of the export
modules: Exporter, Sub::Exporter, Exporter::Declare etc.
Why? No point adding a dependency for something so trivial.
Post by Karen Etheridge
- I'm not sure if this belongs in the Devel namespace - someone else with
better knowledge of the nomenclature would be better able to say.
Nor am I :-)
Post by Karen Etheridge
How does this add value above simply storing a class name in an accessor
(e.g. a Class::Accessor field or Moose attribute)? In moose, I could do
Well, it gives you the reset method so you can *temporarily* override
it. And while the examples I gave are all storing class names, you
could have something more complex there - an object a code-ref,
anything.
Post by Karen Etheridge
has useragent => ( is => 'ro', isa => 'Str', default => 'LWP::UserAgent' );
my $obj = Foo->new(..., useragent => 'Test::LWP::UserAgent');
...to swap out its implementation with something that is mockable.
That becomes unwieldy once you have more than one or two things to mock,
even ignoring the lack of an easy way of only doing it temporarily.
--
David Cantrell | Bourgeois reactionary pig

You don't need to spam good porn
Buddy Burden
2012-10-26 23:39:44 UTC
Permalink
David,
Post by David Cantrell
Also I think that my code's interface is nicer :-)
But we all think that about our interfaces, no? ;->

My personal interface for mocking looks more like this:

class Mock::Property
{
has '_dbh' => ( is => 'ro', default => sub {
$Test::Rent::Dbh } );
has 'businessmodel_tp' => ( is => 'ro', default => 'ppl' );

method commit_changes () {}
}

Test::Rent::mock(
'Company::Property' => 'Mock::Property',
);

And then the mock function just does this little bit of misdirection:

my $mock = Test::MockObject->new;
foreach my $class (keys %classes)
{
# this does several things:
# 1) causes Perl to think the class we're replacing is already loaded
# 2) creates a new() for the replaced class which returns an
instance of the substitute class
# 3) reblesses the returned instance so code will think it
belongs to the replaced class
# 4) uses an AUTOLOAD to catch all methods called on the
replaced class and redirects them
# to the substitute class
no strict 'refs';
use vars '$AUTOLOAD';
$mock->fake_module($class,
new => sub { return bless
$classes{$class}->new, $class },
AUTOLOAD => sub { $AUTOLOAD =~ /::(\w+)\z/; my
$sub = "$classes{$class}::$1"; &$sub },
);
}

so that every time you _ask_ for a Company::Property, you actually
_get_ a Mock::Property. And that's always seemed nice and intuitive
to me. But I've always figured everyone else's intuitive was probably
far enough from mine that I've never tried to stick this out onto
CPAN. (This method also has a number of disadvantages--you can't lie
to code about what class something is without it biting you sooner or
later--but it works surprisingly well, and there are very few problems
that I haven't been able to work around fairly easily.)

I dunno ... your interface is probably a bit nicer than
Test::MockObject's. But I still like mine better. :-D


-- Buddy

Loading...