【问题标题】:How can I use Test::LWP::UserAgent if I cannot replace the $ua in the app code directly?如果我无法直接替换应用代码中的 $ua,我该如何使用 Test::LWP::UserAgent?
【发布时间】:2013-02-19 14:55:44
【问题描述】:

我有一个 sub 通过 REST 服务从 API 检索一些数据。代码比较简单,但是我需要将参数post到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 的地方。

现在我想为此编写一个测试,经过一些research 决定最好使用Test::LWP::UserAgent,这听起来很有希望。不幸的是,有一个问题。在文档中,它说:

请注意,LWP::UserAgent 本身没有猴子补丁 - 您必须使用 这个模块(或子类)来发送你的请求,或者它不能 捕获并处理。

换出用户代理实现的一种常见机制是通过 懒惰构建的 Moose 属性;如果没有提供覆盖 构建时间,默认为 LWP::UserAgent->new(%options)。

哎呀。显然我不能做 Moose 的事情。我也不能只将$ua 传递给潜艇。我当然可以为子添加一个可选的第三个参数$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 功能猴子补丁到 LWP::UserAgent 中,以便我的代码只使用 Test:: one?

【问题讨论】:

  • IMO,可测试性是一个有效的代码问题。编码的目标是拥有“有效”的东西。因此,目标状态的声明将如下所示:“它有效”。然而,经验告诉我:“在测试表明有效之前,没有任何东西‘有效’。”

标签: perl unit-testing lwp


【解决方案1】:

更改您的代码,以便在_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

【讨论】:

  • 感谢您的建议。不过,我更喜欢@rjh 的方法。我不想改变太多。
【解决方案2】:

我当然可以为 sub 添加一个可选的第三个参数 $ua,但我不喜欢这样做的想法。我觉得为了使其可测试而如此彻底地改变这种简单代码的行为是不合适的。

这被称为依赖注入,它是完全有效的。为了进行测试,您需要能够覆盖您的类将用来模拟各种结果的对象。

如果您更喜欢覆盖对象的更隐式方式,请考虑Test::MockObject 和Test::MockModule。您可以模拟 LWP::UserAgent 的构造函数以返回一个测试对象,或者模拟您正在测试的代码的更广泛部分,这样根本不需要 Test::LWP::UserAgent。

另一种方法是重构您的生产代码,以便组件可以(单元)单独测试。从响应处理中拆分 HTTP 获取。然后通过创建自己的响应对象并将其传入来测试第二部分非常简单。

程序员最终会使用上述所有工具。有些适用于单元测试,有些适用于更广泛的集成测试。

【讨论】:

  • 感谢您的建议。当我阅读 T::LWP::UA 文档在 motivation section 中所说的内容时(CPAN 上的大多数模拟库都使用 Test::MockObject,这被广泛认为不是好的做法......)我有点忘记了这两个这个测试。我经常使用 MockModule,但不知何故,这让我完全忘记了。什么 Test::LWP::UserAgent 听起来很棒,想想摆弄自己。 ;-)
【解决方案3】:

从今天开始,我将采用以下方法来解决这个问题。想象一下这段遗留代码1,它不是面向对象的,无法重构,因此它使依赖注入变得容易。

package Foo;
use LWP::UserAgent;

sub frobnicate {
    return LWP::UserAgent->new->get('http://example.org')->decoded_content;
}

这确实很难测试,rjh's answer 是正确的。但是在 2016 年,我们有比 2013 年更多的可用模块。我特别喜欢 Sub::Override,它替换了给定命名空间中的子,但只保留在当前范围内。这使得它非常适合单元测试,因为您不需要关心完成后恢复所有内容。

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::Override 对象。在词法作用域中使用,这种方法非常强大,同时也非常简洁。


1) 这里表示为缺少use strict 和use warnings。

【讨论】:

    猜你喜欢
    • 2014-09-02
    • 2013-12-16
    • 1970-01-01
    • 2018-05-19
    • 1970-01-01
    • 2010-11-20
    • 2011-09-24
    • 2022-01-10
    • 1970-01-01
    相关资源
    最近更新 更多