是否可以在 perl 中模拟或修补输入参数?
Is it possible to mock or patch a input parameter in perl?
好像在python,mock.patch可以patch一个input
是否可以在 perl 中模拟输入参数?如何?这是输出:
not ok 1 - expect: 59, got: 1
我目前的代码:
#!/usr/bin/perl
use Test::More;
use Test::MockObject;
use Date::Calc;
# scope of mocked Date::Calc
{
my $import;
my $mock = Test::MockObject->new();
$mock->fake_module('Date::Calc', import => sub { $import = caller} );
$mock->fake_new('Date::Calc');
$mock->mock('Days_in_Year', sub {print "how to mock parameter - month to be 2\n"});
# is it possible to mock a parameter? how if possible?
my $days_mock = $mock->Days_in_Year(2015,6);
ok($days_mock == 59, "expect: 59, got: $days_mock\n");
}
# unmocked module and methods
my $days_in_year = Date::Calc::Days_in_Year(2015,6);
ok($days_in_year == 181, "expect: 181, got: $days_in_year\n");
done_testing(2);
你不能模拟一个变量,你必须模拟一个依赖项。该依赖项可以是子项、对象,甚至可能是整个 RDBMS。问题中的代码看起来像是证明模拟有效的测试,所以我将在我的示例中尝试坚持这一点。
当你模拟一个对象时,它被称为 dependency injection。稍后我会在回答中谈到这一点。
由于 space 和 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 被覆盖了,但只在给定的范围内。这非常有用,因为它快速、易于记忆且易于阅读,这是需要考虑的一个非常重要的因素。这是一个 made-up 示例。
package Foo;
require Weird::Legacy::Dependency;
sub hello {
my $name = shift;
my $hi = Weird::Legacy::Dependency::rnd_salutation();
return "$hi, $name";
}
在我们需要测试的这段代码中,有一个我们还不能重构的可怕的遗留依赖项。作者喜欢意大利面条,而且这些东西很难辨认。它可能 return 像这样的东西:
Hi, Bob
Hallo, Bob
Good Afternoon, Bob
Բարեւ, Bob
那么我们该如何处理呢?当然,我们重写sub 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
,尽管我们不知道遗留函数会产生什么样的随机内容。
模拟对象
如果您的代码是 object-oriented,您可以使每个依赖项都可注入。这样,您可以控制更多。一个非常典型的例子是数据库连接或 LWP 对象。这是一个简化的例子。此代码可能是某些 API.
的 full-blown 客户端
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 that has a get
method and returns a fixed 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
方法现在总是 return 我们准备好的 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
方法,因此它 return 是我们之前在测试中所做的模拟用户代理对象。
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 and dependency injection to just supply a fake complete database, but a real DBI. That even works with DBIx::Class. On the other hand, if the database code is part of what you want to test, to verify e.g. if the code inserts the right stuff, you can use Test::DatabaseRow. In general, it's good to just look at the Test:: namespace on CPAN. There are some fun things on there. You can mock the time
, or a URI, or the output of external scripts, or even wrap all your tests in individual Moose classes 以一种很好的方式组织您的测试套件。
我建议看一看 Ian Langworth 的 Perl 测试:开发者笔记,其中对这个问题进行了相当广泛的介绍。另一个很好的资源是 Ovid's free test training on github.
1) 请注意,这是一种不好的懒惰!
好像在python,mock.patch可以patch一个input
是否可以在 perl 中模拟输入参数?如何?这是输出:
not ok 1 - expect: 59, got: 1
我目前的代码:
#!/usr/bin/perl
use Test::More;
use Test::MockObject;
use Date::Calc;
# scope of mocked Date::Calc
{
my $import;
my $mock = Test::MockObject->new();
$mock->fake_module('Date::Calc', import => sub { $import = caller} );
$mock->fake_new('Date::Calc');
$mock->mock('Days_in_Year', sub {print "how to mock parameter - month to be 2\n"});
# is it possible to mock a parameter? how if possible?
my $days_mock = $mock->Days_in_Year(2015,6);
ok($days_mock == 59, "expect: 59, got: $days_mock\n");
}
# unmocked module and methods
my $days_in_year = Date::Calc::Days_in_Year(2015,6);
ok($days_in_year == 181, "expect: 181, got: $days_in_year\n");
done_testing(2);
你不能模拟一个变量,你必须模拟一个依赖项。该依赖项可以是子项、对象,甚至可能是整个 RDBMS。问题中的代码看起来像是证明模拟有效的测试,所以我将在我的示例中尝试坚持这一点。
当你模拟一个对象时,它被称为 dependency injection。稍后我会在回答中谈到这一点。
由于 space 和 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 被覆盖了,但只在给定的范围内。这非常有用,因为它快速、易于记忆且易于阅读,这是需要考虑的一个非常重要的因素。这是一个 made-up 示例。
package Foo;
require Weird::Legacy::Dependency;
sub hello {
my $name = shift;
my $hi = Weird::Legacy::Dependency::rnd_salutation();
return "$hi, $name";
}
在我们需要测试的这段代码中,有一个我们还不能重构的可怕的遗留依赖项。作者喜欢意大利面条,而且这些东西很难辨认。它可能 return 像这样的东西:
Hi, Bob
Hallo, Bob
Good Afternoon, Bob
Բարեւ, Bob
那么我们该如何处理呢?当然,我们重写sub 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
,尽管我们不知道遗留函数会产生什么样的随机内容。
模拟对象
如果您的代码是 object-oriented,您可以使每个依赖项都可注入。这样,您可以控制更多。一个非常典型的例子是数据库连接或 LWP 对象。这是一个简化的例子。此代码可能是某些 API.
的 full-blown 客户端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 that has a get
method and returns a fixed 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
方法现在总是 return 我们准备好的 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
方法,因此它 return 是我们之前在测试中所做的模拟用户代理对象。
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 and dependency injection to just supply a fake complete database, but a real DBI. That even works with DBIx::Class. On the other hand, if the database code is part of what you want to test, to verify e.g. if the code inserts the right stuff, you can use Test::DatabaseRow. In general, it's good to just look at the Test:: namespace on CPAN. There are some fun things on there. You can mock the time
, or a URI, or the output of external scripts, or even wrap all your tests in individual Moose classes 以一种很好的方式组织您的测试套件。
我建议看一看 Ian Langworth 的 Perl 测试:开发者笔记,其中对这个问题进行了相当广泛的介绍。另一个很好的资源是 Ovid's free test training on github.
1) 请注意,这是一种不好的懒惰!