Beefy Boxes and Bandwidth Generously Provided by pair Networks
Just another Perl shrine
 
PerlMonks  

Smallish mock objects

by diotalevi (Canon)
on Dec 16, 2006 at 00:12 UTC ( [id://590163]=CUFP: print w/replies, xml ) Need Help??

I wanted to use Test::MockObject but didn't feel like arguing with anyone to get it installed so I invented a quickie little way to make mock objects.

use Symbol 'gensym'; # Create a new object in a new class. gensym() isn't strictly required +. Anything that makes namespaces would be sufficient. This is just co +nvenient. my $obj = gensym; my $class = *$obj{NAME}; bless $obj, $class; # Install a method *{"$class\::a_method"} = sub { whatever };

Replies are listed 'Best First'.
Re: Smallish mock objects
by Ovid (Cardinal) on Dec 16, 2006 at 08:32 UTC

    Can you explaint to me why mock objects are so great? I've used them, but I find that I sometimes make inappropriate interface assumptions and lose some integration testing as a result (in other words, if my mocked objects have subtle differences from the real ones, my tests might pass but they're not correct). I've always preferred Test::MockModule and just replacing the exact target code that might prove problematic in something I might otherwise mock. This allows me to at least test partial integration with the object in question.

    I do realize that some module simple can't be loaded easily (some of the Apache ones, for example), so mock objects would be appropriate there, but otherwise, I don't think it's worth the trouble.

    Cheers,
    Ovid

    New address of my CGI Course.

      I prefer Test::MockObject::Extends these days. The nice point is just as you say; when you need really precise control either to make something happen that's difficult to invoke normally or to work around something really complex that is difficult to test.

      Complex server initialization or failure types are the two main items that come to my mind.

      I do realize that some module simple can't be loaded easily (some of the Apache ones, for example), so mock objects would be appropriate there, but otherwise, I don't think it's worth the trouble.

      I only find myself using them when I have some state in the "mock" object that's hard to reproduce in a "live" object (e.g rare error conditions, etc.) or when I'm dealing with third party code.

      I have to admit I rarely use any of the CPAN modules for mocking and do something like:

      { package MockFoo; sub new { bless {}, shift } sub rarely_returns_42 { return 42 } } my $mock = MockFoo->new;

      rather than:

      use Test::MockObject; my $mock = Test::MockObject->new->set_always( rarely_returns_42 => 42 );

      Probably because I don't use them enough for the API to be familiar to me.

      The pro-/anti-mock argument seems to occur every few months on the TDD mailing list :-) Google around for "state based testing" vs "interaction based testing".

        Your last comment is very interesting. I'll have a look to state based testing vs interaction based testing, thanks

        coming back to the thread, I've always felt that mock objects shine when you *actually* want to be in control:

      • you want to force failures all over the place
      • when you can record "external contexts" so you have state, and you associate your mock object with this external state. Pre-conditions and post-conditions (can be other processes!) can insure invariants. An example would be when your test suite incorporates snapshots of data in a few small (almost) flat databases (say switching on and off at will your big fat Oracle). Needless to say that collecting the data becomes part of the tests.
      • Actually, "internal testing" is the thing most talked about when the real situation is closer to "external testing", I guess. If we look at Chromatic's and Ian Langworth's "perl testing" the ratio is about 10 to one. Even unitary testing if often a mix, and it seems that a generic way to approach "external testing" mechanics would be very useful (if you have pointers about something actually general enough but still practical, IŽll be grateful).

        Maybe I am a paranoid, but I build my systems (even small prototypes) making them fail all over the place. Thinking early about recovery seems to make them better (not really something rational, more like a gut feeling ;)...probably because this over-emphasizes data sources.

        hth --stephan

      I haven't used mock objects much - most of what I've tested hasn't either required objects or the real things have been easy enough to instantiate in place. Right now it was far easier to just get an object that satisfied $obj->foo == 1 than it was to get the real thing and get it to return true or false. I wasn't testing the entire suite - I just want to test a single function that's getting refactored and need to supply differing boolean values.

      Incidentally, it was Devel::Spy which told me I needed to go treat this method as a boolean. Neat module. Glad I wrote it. I hope I'll get use out of it more than once.

      ⠤⠤ ⠙⠊⠕⠞⠁⠇⠑⠧⠊

Re: Smallish mock objects
by ysth (Canon) on Dec 25, 2006 at 01:59 UTC
    Oog. I didn't know you could bless globs. Should you be able to? My first reaction is to call it a bug that it doesn't say "Can't bless non-reference value". Update: I was misremembering gensym as returning a glob; in fact it returns a globref and there's nothing wrong with blessing that.

    gensym doesn't actually create namespaces; they spring into existence on demand. I would just have done something like:

    my $obj = {}; my $class = "$obj"; bless $obj, $class; *{"${class}::foo"} = sub { "bar" }; print $obj->foo();

      Oog. I didn't know you could bless globs

      ( Globs Glob refs ) are blessable and globs are tieable. Similarly, all IOs are blessed.

      >perl -e "open $fh, '-'; print *$fh{IO} IO::Handle=IO(0x22532c) >perl -e "print *STDOUT{IO} IO::Handle=IO(0x225cfc)

      That means all IOs can be used as objects.

      >perl -e "STDOUT->flush() Can't locate object method "flush" via package "IO::Handle" >perl -e "use IO::Handle; STDOUT->flush()

      You can have overlapping classes here. If $obj expires prior to this code executing again then you can have the same location-reused and you'd have the same class name for two different objects. Symbol does the work of maintaining a counter for me and applying it to a namespace. The following code is quite a bit lighter but it introduces the namespace Symbol::GEN1->GENinf.

      my $counter = 0; sub new { my $class = "Symbol::GEN$counter"; ++ $counter; return bless ..., $class; }

      ⠤⠤ ⠙⠊⠕⠞⠁⠇⠑⠧⠊

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: CUFP [id://590163]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others goofing around in the Monastery: (3)
As of 2024-04-18 04:12 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found