似乎在python中,mock.patch可以修补输入
是否可以在perl中模拟输入参数?如何?这是输出:
-fx-background-fill: yellow;
到目前为止的代码:
not ok 1 - expect: 59, got: 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 time
,URI或the output of external scripts,甚至wrap all your tests in individual Moose classes以良好的方式组织您的测试套件。
我建议看一下Ian Langworth的 Perl Testing:A Developer's Notebook ,它给出了相当广泛的介绍。另一个好的资源是Ovid的free test training on github。
1)请注意,这是一种糟糕的行为!