Unit-testing and mock objects

J

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:

=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
 
B

Ben Morrow

Quoth "jmv said:
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
 
J

jmv

Ben said:
Quoth "jmv" <[email protected]>:
[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
 
A

anno4000

jmv said:
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
 
B

Ben Morrow

Quoth "jmv said:
Ben said:
Quoth "jmv" <[email protected]>:
[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
 
J

jmv

Ben said:
[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
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,766
Messages
2,569,569
Members
45,042
Latest member
icassiem

Latest Threads

Top