2013-02-19 15 views
3

我有一個通過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

現在我想寫一個測試,並在一些research決定後最好使用Test::LWP::UserAgent,這聽起來真的很有希望。不幸的是,有一個問題。在DOC,它說:

注意,LWP :: UserAgent的本身不是猴子打補丁 - 你必須使用 該模塊(或子類)發送請求,或者它不能 捕獲並處理。

換出useragent實現的一個常見機制是通過一個 延遲構建的Moose屬性;如果在 施工時間未提供覆蓋,則默認爲LWP :: UserAgent-> new(%選項)。

Arghs。顯然我不能做麋鹿的事情。我不能僅僅通過一個$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' 
); 

是否有猴補丁測試:: LWP :: UserAgent的功能集成到LWP :: UserAgent的,這樣的方式我的代碼只是使用Test :: one?

+0

IMO,可測性是一個有效的代碼問題。編碼的目標是有一些「有效」的東西。所以,目標狀態的陳述會是這樣的:「它有效」。然而,經驗告訴我:「除非測試表明它沒有任何效果。」 – Axeman 2013-02-19 16:49:45

回答

2

我當然可以添加一個可選的第三參數$ ua的子,但我不喜歡這樣做的想法。我覺得改變這樣簡單的代碼的行爲是不行的,因爲這樣做只是爲了使其可測試。

這被稱爲依賴注入,它是完全有效的。爲了測試你需要能夠覆蓋你的類將用來模擬各種結果的對象。

如果您更喜歡以更隱式的方式重寫對象,請考慮Test::MockObjectTest::MockModule。你可以模擬LWP :: UserAgent的構造函數來返回一個測試對象,或者模擬你正在測試的代碼的一大部分,這樣Test :: LWP :: UserAgent根本就不需要。

另一種方法是重構您的生產代碼,使得組件可以獨立測試。從處理響應中拆分HTTP抓取。然後通過創建自己的響應對象並將其傳入,測試第二部分非常簡單。

最終程序員使用上述所有工具。有些適用於單元測試,其他適用於更廣泛的集成測試。

+0

感謝您的建議。當我讀到T :: LWP :: UA doc在[動機部分](https://metacpan.org/module/Test::LPP::UserAgent#MOTIVATION)中所說的內容時(* CPAN上的大多數模擬庫使用Test :: MockObject,這被廣泛認爲是不好的做法... *)我有點忘了這兩個測試。我使用MockModule很多,但不知何故,我的思緒完全在這裏。什麼Test :: LWP :: UserAgent確實聽起來很好,想想擺弄自己。 ;-) – simbabque 2013-02-20 08:55:51

4

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

感謝您的建議。不過,我更喜歡@ rjh的方法。我不想改變太多。 – simbabque 2013-02-20 10:04:57

0

截至今天,我會去用下面的辦法解決這個問題。想象一下這段傳統代碼,它不是面向對象的,不能被重構,因此它使依賴注入變得容易。

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對象,我們養活我們所有的測試案例。我們也可以給它一個代碼ref,如果我們想要的話,它會對請求運行測試(這裏沒有顯示)。然後,我們使用Sub :: Override使LWP :: UserAgent的構造函數不返回實際的LWP :: UA,但是已經準備好了$rigged_ua。然後我們運行我們的測試。一旦$sub超出範圍,LWP::UserAgent::new得到恢復,我們不干涉其他任何事情。

重要的是始終以儘可能小的範圍進行這些測試(如Perl中的大部分內容)。

如果有很多這樣的測試用例,爲每個請求創建某種類型的配置哈希是一種很好的策略,並且使用一個構建輔助函數來創建綁定的用戶代理,另一種方法是創建Sub :: Override對象。在詞法範圍中使用,這種方法同時非常強大和簡潔。


1)表示這裏由缺乏use strictuse warnings

相關問題