由另一个模块内部使用的模拟Git模块

时间:2018-09-19 12:40:09

标签: perl unit-testing mocking

我正在尝试对使用来自CPAN的Importer::Git的模块Git.pm进行单元测试,我想模拟对Git::commandGit::repository和{{1 }}等不会真正更改我的文件系统。我试图通过Test :: MockObject做到这一点,但似乎我还没有完全了解其内部工作原理...

示例:

Git::command_oneline

测试用例:

package Importer::Git

   sub create_repository {
    my ( $rc, $repo );

    $rc = Git::command_oneline( 'init', $self->targetdir . "/" . $self->name );                                                                                                                                                                                                      

    $repo = Git->repository( Directory => $self->targetdir . "/" . $self->name );
    $rc = $repo->command( 'config', 'user.name', $self->git_import_user->{ name } );

      $self->_repo( $repo );

      return $repo;
   }

但是它似乎并没有取代有问题的git对象,因为该测试崩溃时来自实际Git.pm模块的消息。

use Import::Git;

use Test::More tests => 1;    # last test to print
use Test::Exception;
use Test::MockObject;

# pretend to have loaded the Git Module.
$INC{'Git.pm'} = 1;
my $git = Test::MockObject->new();
$git->fake_module('Git', repository => sub { $git } );
$git->set_true( qw(command command_oneline) );


$repo = Import::Git->init();
$repo->targetdir('./');
$repo->name('testrepo');
$repo->git_import_user({ name => 'test', email => 'test@test.com', push_default => 'testpush' });
$repo->create_repository();

我猜在Importer :: Git的这两行中,它不能代替$ repo

error: Malformed value for push.default: testpush
error: Must be one of nothing, matching, simple, upstream or current.
fatal: bad config variable 'push.default' in file '/home/.../testrepo/.git/config' at line 10
init .//testrepo: command returned error: 128

那么我该如何正确模拟呢?我希望 $repo = Git->repository( Directory => $self->targetdir . "/" . $self->name ); $rc = $repo->command( 'config', 'user.name', $self->git_import_user->{ name } ); 调用仅返回1。

更新:

戴夫斯猜想是对的。更正代码以解决此问题:

$repo->command

2 个答案:

答案 0 :(得分:3)

这只是一个猜测,我没有时间进行测试...

The documentation对于fake_module()方法来说是这样的:

  

请注意,这必须在实际模块有机会加载之前进行。在使用前将其包装在BEGIN块中,或者在use_ok()require_ok()调用之前将其包装。

在加载模块之前,我希望您的模块(是Import::Git还是Importer::Git?) loads Git.pm , so you need to call fake_module()`。类似这样的东西,也许:

use Test::More tests => 1;    # last test to print
use Test::Exception;
use Test::MockObject;

my $git;

BEGIN {
  # pretend to have loaded the Git Module.
  $INC{'Git.pm'} = 1;
  $git = Test::MockObject->new();
  $git->fake_module('Git', repository => sub { $git } );
  $git->set_true( qw(command command_oneline) );
}

use Import::Git;

答案 1 :(得分:1)

Dave's excellent answer的替代方法是完全不使用fake_module功能,而使用Sub::Override之类的方法临时覆盖构造函数。我觉得这可以提供更精细的控制。

use Test::More;
use Test::MockObject;
use Sub::Override;
use Git (); # we need this so we can override it in case it hasn't been loaded yet

# test code ... 

{
    # this is our faked module
    my $git = Test::MockObject->new;
    $git->set_true( qw(command command_oneline) );

    # we will save the arguments to the constructor here for 
    # inspection in tests later
    my @constructor_args;

    # this will temporarily replace the constructor until $sub
    # goes out of scope
    my $sub = Sub::Override->new(
        'Git::new' => sub {
            @constructor_args = @_; # save args
            return $git;            # return fake
        }
    );

    is something_that_deals_with_git(), $what_you_expect, 'test stuff with git';
    is scalar @constructor_args, 2, '... and Git constructor was called with 2 args';
    # ...
}

它的缺点是您不能使用Test::MockObject's facilities to look at the callsnew,但是有一个简单的方法可以通过我们的@constructor_args变量来减轻这种情况。其他所有内容都保持不变。