Unit-testing and mock objects

Discussion in 'Perl Misc' started by jmv, Oct 6, 2006.

  1. jmv

    jmv Guest

    Hello,
    I have a problem creating mock objects for the unit tests. I'm writing
    a unit tests for the rather old proprietary OO application that has all
    sort of code smells.

    The code I want to test looks like this:

    =cut=
    use Corp::System; # exports runCommand()
    use Logcheck;

    sub new { ... }

    sub createUser {
    ...
    my $exitval = runCommand("useradd ...");
    ...
    Logcheck::createLogDir();
    ...
    }
    =cut=


    I want to override the runCommand method from Corp::System module. I've
    found only one way to do it. In my unit test module I write:

    =cut=
    BEGIN {
    use Corp::System;
    package Corp::System;
    sub runCommand { push @COMMANDS, shift; return 0; }
    1;
    }
    =cut=

    But this method is very boring, unelegant and error-prone, because sub
    createUser, which I'm testing, uses subroutines from a lot of modules
    and there're numerous dependencies between them.

    Is there more elegant and flexible method for testing such classes
    without refactoring them? In any case, tests should exist before
    refactoring.

    Eugene
    jmv, Oct 6, 2006
    #1
    1. Advertising

  2. jmv

    Ben Morrow Guest

    Quoth "jmv" <>:
    > Hello,
    > I have a problem creating mock objects for the unit tests. I'm writing
    > a unit tests for the rather old proprietary OO application that has all
    > sort of code smells.
    >
    > The code I want to test looks like this:

    [snip]
    >
    > I want to override the runCommand method from Corp::System module. I've
    > found only one way to do it. In my unit test module I write:

    [snip]
    >
    > Is there more elegant and flexible method for testing such classes
    > without refactoring them? In any case, tests should exist before
    > refactoring.


    A search on CPAN for 'mock' reveals Test::MockModule. Is this the sort
    of thing you mean?

    Ben

    --
    The cosmos, at best, is like a rubbish heap scattered at random.
    Heraclitus
    Ben Morrow, Oct 6, 2006
    #2
    1. Advertising

  3. jmv

    jmv Guest

    Ben Morrow wrote:
    > Quoth "jmv" <>:
    > [snip]
    > A search on CPAN for 'mock' reveals Test::MockModule. Is this the sort
    > of thing you mean?


    Thank you, I didn't see this module. But I'm afraid it wouldn't work.
    I've tried Test::MockObject and Sub::Override and they doesn't handle
    this situation (overriding subs in module B which is used by module A
    which is used by unit-test).
    Eugene
    jmv, Oct 7, 2006
    #3
  4. jmv

    -berlin.de Guest

    jmv <> wrote in comp.lang.perl.misc:
    > Hello,
    > I have a problem creating mock objects for the unit tests. I'm writing
    > a unit tests for the rather old proprietary OO application that has all
    > sort of code smells.
    >
    > The code I want to test looks like this:
    >
    > =cut=
    > use Corp::System; # exports runCommand()
    > use Logcheck;
    >
    > sub new { ... }
    >
    > sub createUser {
    > ...
    > my $exitval = runCommand("useradd ...");
    > ...
    > Logcheck::createLogDir();
    > ...
    > }
    > =cut=
    >
    >
    > I want to override the runCommand method from Corp::System module.


    I don't see how runCommand is a method, it isn't called as one. So
    inheritance-based overriding is out.

    > I've
    > found only one way to do it. In my unit test module I write:
    >
    > =cut=
    > BEGIN {
    > use Corp::System;
    > package Corp::System;
    > sub runCommand { push @COMMANDS, shift; return 0; }
    > 1;


    The return value of a BEGIN block is ignored. It isn't the same
    as module. "1;" isn't needed. I wonder if you even need the
    BEGIN block.

    > }
    > =cut=
    >
    > But this method is very boring, unelegant and error-prone, because sub
    > createUser, which I'm testing, uses subroutines from a lot of modules
    > and there're numerous dependencies between them.


    I'm not sure what exactly you dislike about your method. If there
    are many subroutines to override, there will be that many actions
    to take, and interdependencies will make life harder. There's no
    way a clever overriding method will change that.

    > Is there more elegant and flexible method for testing such classes
    > without refactoring them? In any case, tests should exist before
    > refactoring.


    You can change the behavior of Corp::System::runCommand() any time
    and from any place through

    *Corp::System::runCommand = sub { ...};

    It doesn't have to happen at compile time as long as it happens before
    the first call.

    Anno
    -berlin.de, Oct 7, 2006
    #4
  5. jmv

    Ben Morrow Guest

    Quoth "jmv" <>:
    >
    > Ben Morrow wrote:
    > > Quoth "jmv" <>:
    > > [snip]
    > > A search on CPAN for 'mock' reveals Test::MockModule. Is this the sort
    > > of thing you mean?

    >
    > Thank you, I didn't see this module. But I'm afraid it wouldn't work.
    > I've tried Test::MockObject


    This is no good. You're not using objects, just simple exported subs.

    > and Sub::Override and they doesn't handle
    > this situation (overriding subs in module B which is used by module A
    > which is used by unit-test).


    You have to mock the sub in the module that uses it, not the one it
    originated from. That is, if you have

    package AA;

    sub foo {
    return 'foo';
    }

    and

    package BB;

    use AA;

    sub bar {
    return 'bar' . foo;
    }

    (Exporter &c. omitted for clarity) then you need

    use Test::MockModule;

    {
    my $M = Test::MockModule->new('BB');
    $M->mock(foo => sub { return 'baz' });

    # tests
    }

    in your test file. I can see this may be a pain if you have a sub that
    is imported all over the place and you want to mock it the same
    everywhere; it should be a small matter of programming to run through
    the symbol table and find everywhere this sub has been exported to...

    Ben

    --
    You poor take courage, you rich take care:
    The Earth was made a common treasury for everyone to share
    All things in common, all people one. []
    'We come in peace'---the order came to cut them down.
    Ben Morrow, Oct 8, 2006
    #5
  6. jmv

    jmv Guest

    Ben Morrow wrote:
    > [snip]
    > in your test file. I can see this may be a pain if you have a sub that
    > is imported all over the place and you want to mock it the same
    > everywhere; it should be a small matter of programming to run through
    > the symbol table and find everywhere this sub has been exported to...


    Thanks, that was very helpful answer. I'm new to using mock objects (my
    usual unit-tests were much simpler before that project) and overriding
    subs/modules, so I didn't find out that myself.
    Eugene
    jmv, Oct 9, 2006
    #6
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Joe Van Dyk

    Mock objects and testing

    Joe Van Dyk, Apr 8, 2006, in forum: C++
    Replies:
    3
    Views:
    391
    Daniel T.
    Apr 8, 2006
  2. Fuzzyman
    Replies:
    0
    Views:
    256
    Fuzzyman
    Aug 22, 2009
  3. Ulrich Eckhardt

    unit-profiling, similar to unit-testing

    Ulrich Eckhardt, Nov 16, 2011, in forum: Python
    Replies:
    6
    Views:
    317
    Roy Smith
    Nov 18, 2011
  4. Bill Mosteller
    Replies:
    0
    Views:
    209
    Bill Mosteller
    Oct 22, 2009
  5. James Harris

    C unit testing and regression testing

    James Harris, Aug 8, 2013, in forum: C Programming
    Replies:
    40
    Views:
    554
    Les Cargill
    Aug 17, 2013
Loading...

Share This Page