[英]How can I use Test::LWP::UserAgent if I cannot replace the $ua in the app code directly?
我有一个sub通过REST服务从API检索一些数据。 代码相当简单,但我需要将参数发布到API,我需要使用SSL,所以我必须通过LWP :: UserAgent并且不能使用LWP :: Simple 。 这是它的简化版本。
sub _request {
my ( $action, $params ) = @_;
# User Agent fuer Requests
my $ua = LWP::UserAgent->new;
$ua->ssl_opts( SSL_version => 'SSLv3' );
my $res = $ua->post(
$url{$params->{'_live'} ? 'live' : 'test'}, { action => $action, %$params }
);
if ( $res->is_success ) {
my $json = JSON->new;
return $json->decode( $res->decoded_content );
} else {
cluck $res->status_line;
return;
}
}
这是我模块中唯一的地方(不是OOp),我需要$ua
。
现在我想为此编写一个测试,经过一些研究决定最好使用Test :: LWP :: UserAgent ,这听起来真的很有意思。 不幸的是,有一个问题。 在文档中,它说:
请注意,LWP :: UserAgent本身不是猴子修补程序 - 您必须使用此模块(或子类)发送请求,否则无法捕获和处理它。
换出useragent实现的一种常见机制是通过一个延迟构建的Moose属性; 如果在构造时没有提供覆盖,则默认为LWP :: UserAgent-> new(%options)。
Arghs。 显然我不能做麋鹿的事情。 我也不能将$ua
传递给sub。 我当然可以在sub中添加一个可选的第三个param $ua
,但我不喜欢这样做的想法。 我觉得改变这种简单代码的行为是不可能的,只是为了让它可以测试。
我基本上想要做的是像这样运行我的测试:
use strict;
use warnings;
use Test::LWP::UserAgent;
use Test::More;
require Foo;
Test::LWP::UserAgent->map_response( 'www.example.com',
HTTP::Response->new( 200, 'OK',
[ 'Content-Type' => 'text/plain' ],
'[ "Hello World" ]' ) );
is_deeply(
Foo::_request('https://www.example.com', { foo => 'bar' }),
[ 'Hello World' ],
'Test foo'
);
有没有办法将Test :: LWP :: UserAgent功能monkeypatch到LWP :: UserAgent,以便我的代码只使用Test :: one?
更改您的代码,以便在_request()
,您调用_ua()
来收集您的用户代理并在您的测试脚本中覆盖此方法。 像这样:
在你的模块中:
sub _request {
...
my $ua = _ua();
...
}
sub _ua {
return LWP::UserAgent->new();
}
在您的测试脚本中:
...
Test::More::use_ok('Foo');
no warnings 'redefine';
*Foo::_ua = sub {
# return your fake user agent here
};
use warnings 'redefine';
... etc etc
我当然可以在sub中添加一个可选的第三个param $ ua,但我不喜欢这样做的想法。 我觉得改变这种简单代码的行为是不可能的,只是为了让它可以测试。
这称为依赖注入,它完全有效。 对于测试,您需要能够覆盖您的类将用于模拟各种结果的对象。
如果您更喜欢覆盖对象的更隐式方法,请考虑Test :: MockObject和Test :: MockModule 。 您可以模拟LWP :: UserAgent的构造函数来返回测试对象,或者模拟您正在测试的代码的更大部分,以便根本不需要Test :: LWP :: UserAgent。
另一种方法是重构您的生产代码,使组件(单元)可以单独测试。 从处理响应中分离HTTP提取。 然后通过创建自己的响应对象并将其传入,测试第二部分非常简单。
最终程序员使用上述所有工具。 一些适用于单元测试,另一些适用于更广泛的集成测试。
截至今天,我将采用以下方法来解决这个问题。 想象一下这段遗留代码1 ,它不是面向对象的,不能重构,因此它使依赖注入变得容易。
package Foo;
use LWP::UserAgent;
sub frobnicate {
return LWP::UserAgent->new->get('http://example.org')->decoded_content;
}
这测试确实很棘手,而且rjh的答案是正确的 。 但是在2016年,我们可以获得比2013年更多的模块。我特别喜欢Sub :: Override ,它取代了给定命名空间中的sub,但只保留在当前范围内。 这使它非常适合单元测试,因为在完成后你不需要关心恢复所有内容。
package Test::Foo;
use strict;
use warnings 'all';
use HTTP::Response;
use Sub::Override;
use Test::LWP::UserAgent;
use Test::More;
# create a rigged UA
my $rigged_ua = Test::LWP::UserAgent->new;
$rigged_ua->map_response(
qr/\Qexample\E/ => HTTP::Response->new(
'200',
'OK',
[ 'Content-Type' => 'text/plain' ],
'foo',
),
);
# small scope for our override
{
# make LWP return it inside our code
my $sub = Sub::Override->new(
'LWP::UserAgent::new'=> sub { return $rigged_ua }
);
is Foo::frobnicate(), 'foo', 'returns foo';
}
我们基本上创建一个Test :: LWP :: UserAgent对象,我们提供所有测试用例。 我们还可以给它一个代码引用,如果我们想要的话,将在请求上运行测试(这里没有显示)。 然后我们使用Sub :: Override使LWP :: UserAgent的构造函数不返回实际的LWP :: UA,而是已经准备好的$rigged_ua
。 然后我们运行我们的测试。 一旦$sub
超出范围, LWP::UserAgent::new
就会恢复,我们不会干扰其他任何事情。
始终以尽可能小的范围进行这些测试非常重要(就像Perl中的大多数事情一样)。
如果有很多测试用例,那么为每个请求构建某种配置哈希是一个很好的策略,并使用构建助手函数来创建被装配的用户代理,另一个用于创建Sub: :覆盖对象。 在词法范围中使用,这种方法非常强大,同时非常简洁。
1)这里表示缺乏use strict
和use warnings
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.