是否可以在perl中模拟或修补输入参数?

时间:2016-01-26 02:02:06

标签: perl unit-testing mocking

似乎在python中,mock.patch可以修补输入

是否可以在perl中模拟输入参数?如何?这是输出:

-fx-background-fill: yellow;

到目前为止的代码:

not ok 1 - expect: 59, got: 1

1 个答案:

答案 0 :(得分:5)

你不能模拟一个变量,你必须模拟一个依赖。该依赖关系可以是子,对象,甚至可以是整个RDBMS。问题中的代码看起来像是一个证明模拟工作的测试,所以我会在我的例子中坚持使用它。

模拟对象时,称为dependency injection。我将在后面的答案中讨论这个问题。

出于空间和laziness的原因,请假设此答案中的每一段代码都以:

开头
use strict; 
use warnings;

嘲笑一个子

由于代码的设计方式,有时候这是不可能的。在这种情况下,您需要模拟一个函数(在Perl中是一个子函数)或两个,或者可能是整个模块。后者几乎不需要。

覆盖单个子(或多个)的最简单方法是Sub::Override。它对单元测试很有用,但不限于此。

use Test::More;
use Sub::Override;
use DateTime;

my $dt = DateTime->now;

{   # scoped in this block
    my $override = Sub::Override->new( 'DateTime::year', sub { 2015 } );
    is $dt->year, 2015, 'Year is 2015';
}

isnt $dt->year, 2015, 'Year is NOT 2015';

done_testing;

__END__
ok 1 - Year is 2015
ok 2 - Year is NOT 2015
1..2

正如我们所看到的,sub被覆盖了,但只在给定的范围内。这非常有用,因为它快速,易记,易读,这是一个非常重要的考虑因素。这是一个简单的例子。

package Foo;
require Weird::Legacy::Dependency;

sub hello {
    my $name = shift;
    my $hi = Weird::Legacy::Dependency::rnd_salutation();
    return "$hi, $name";
}

在我们需要测试的代码中,有一个可怕的遗留依赖,我们无法重构。作者喜欢spagetti,这些东西很难辨认。它可能会返回这样的东西:

Hi, Bob
Hallo, Bob
Good Afternoon, Bob
Բարեւ, Bob

那我们该怎么处理呢?当然,我们覆盖了子rnd_salutation

use Test::More;
use Sub::Override;

{   # scoped in this block
    my $override = Sub::Override->new(
      'Weird::Legacy::Dependency::rnd_salutation', sub { 'Hi' } );
    is Foo::hello('Joe'), 'Hi, Joe', 'Say hi to Joe';
}

现在我们可以确保hello完全<given_salutation>, $name,但我们不知道遗留函数会出现什么样的随机内容。

模拟对象

如果您的代码是面向对象的,则可以使每个依赖项都可注入。这样,你可以控制更多。一个非常典型的例子是数据库连接或LWP对象。这是一个简化的例子。对于某些API,此代码可能是一个成熟的客户端。

package My::WebserviceClient;
use Moose;
use LWP::UserAgent;

has ua => (
    is      => 'ro',
    isa     => 'LWP::UserAgent',
    default => sub { LWP::UserAgent->new },
);

sub call {
    my ($self, $url) = @_;

    my $res = $self->ua->get($url);
    return $res->content if $res->is_success;
}

package main;

my $client = My::WebserviceClient->new;
print length $client->call('http://www.example.org');

现在要测试一下,我们不希望它真正去取东西。所以我们需要嘲笑它。让我们制作一个mock object方法get并返回固定的HTTP::Response

use Test::More;
use Test::MockObject;
use HTTP::Response;
use My::WebserviceClient;

# prepare the mock object
my $mock = Test::MockObject->new;
$mock->set_isa('LWP::UserAgent');

# set up a response object
my $res = HTTP::Response->new( 200, 'OK', [], 'Hello' );
$mock->set_always( 'get', $res );

# here we INJECT the DEPENDENCY
my $client = My::WebserviceClient->new( ua => $mock );

is $client->call('http://www.example.org/'), 'Hello', 
  'Just the content is returned';

done_testing;

__END__
ok 1 - Just the content is returned

这样可行,因为模拟用户代理中的get方法现在总是返回我们准备好的HTTP :: Response对象。这样,我们还可以测试程序是否正确处理404重复。

同时嘲笑

但有时候无法注入依赖项。如果该程序的作者过于懒惰 1 为用户代理设置属性并执行此操作会怎么样?

package My::WebserviceClient;
use Moose;
use LWP::UserAgent;

sub call {
    my ( $self, $url ) = @_;

    my $res = LWP::UserAgent->new->get($url);
    return $res->content if $res->is_success;
}

现在注射不再起作用了。我们需要做点别的事。 Test :: MockObject不鼓励使用它的fake_module方法,因为Test :: MockModule可以做得更好。我们需要使用它来模拟LWP :: UserAgent中的new方法,因此它返回我们之前在测试中所做的模拟用户代理对象。

use Test::More;
use Test::MockObject;
use Test::MockModule;
use HTTP::Response;
use My::WebserviceClient;

# prepare the mock object
my $mock_ua = Test::MockObject->new;
$mock_ua->set_isa('LWP::UserAgent');

# set up a response object
my $res = HTTP::Response->new( 200, 'OK', [], 'Hello' );
$mock_ua->set_always( 'get', $res );

# Now we need to mock LWP::UserAgent's new to return our
# mocked object
{
    my $module = Test::MockModule->new('LWP::UserAgent');
    $module->mock( 'new', sub { return $mock_ua } );

    my $client = My::WebserviceClient->new;
    # inside of call, it will now use our mocked LWP::UA::new
    is $client->call('http://www.example.org/'), 'Hello', 
      'Just the content is returned';
}

done_testing;

__END__
ok 1 - Just the content is returned

当然在这种情况下我们也可以使用Sub :: Override。我认为这是一个偏好的问题。

另请注意,有Test::LWP::UserAgent,它为模拟用户代理的特定情况提供了许多不错的功能。我刚刚选择了LWP作为一个简单的例子。对于真正的代码,我更喜欢Test :: LWP :: UserAgent。

嘲笑其他东西

如果你需要处理数据库(比如MySQL),最好使用DBD::sqlite和依赖注入来提供一个假的完整数据库,但是真正的DBI。这甚至适用于DBIx::Class。另一方面,如果数据库代码是您要测试的内容的一部分,则要验证,例如如果代码插入正确的内容,您可以使用Test::DatabaseRow。通常,只需查看CPAN上的Test :: namespace即可。那里有一些有趣的东西。您可以mock the timeURIthe output of external scripts,甚至wrap all your tests in individual Moose classes以良好的方式组织您的测试套件。

我建议看一下Ian Langworth的 Perl Testing:A Developer's Notebook ,它给出了相当广泛的介绍。另一个好的资源是Ovidfree test training on github

1)请注意,这是一种糟糕的行为!