2012-10-19 24 views
10

這是我第一個問題Stack Overflow。如果我違反了一些規則,請事先道歉。用Test :: More測試Perl模塊(中級Perl,第14章)

我一直在閱讀中級Perl第2版的第14章,其中討論了測試Perl模塊和使用Test :: More中的特性。我指的是在本書的「添加我們的第一次測試」一節中直接發佈的代碼。

對於一些背景,在本章中,樣本Animal類是在具有相同名稱的模塊中創建的。這個類有一個簡單的speak方法,該方法是這樣的:

sub speak { 
    my $class = shift; 
    print "a $class goes ", $class->sound, "!\n"; 
} 

sound方法是一個簡單的字符串返回一個特定的動物,因此,例如,一匹馬的sound方法將會更加簡單sub sound { "neigh" }和它的speak方法應輸出以下內容:

A Horse goes neigh! 

我快到的問題是:在測試代碼中,我在./Animal/t/Animal.t創建的,我指示使用裸塊和Test::More::is到測試那個speak方法正在工作。代碼如下所示:

[test code snip] 
{ 
    package Foofle; 
    use parent qw(Animal); 

    sub sound { 'foof' } 
    is(Foofle->speak, 
     "A Foofle goes foof!\n", 
     "An Animal subclass does the right thing" 
    ); 
} 

測試失敗。我跑了所有的生成命令,但運行時「搭建測試」,我得到這個故障的動物試驗:

Undefined subroutine &Foofle::is called at t/Animal.t line 28. 

當我明確地使用Test::More::is,而不是隻是普通is,測試還是失敗的以下消息:

# Failed test 'An Animal subclass does the right thing' 
# at t/Animal.t line 28. 
#   got: '1' 
#  expected: 'A Foofle goes foof! 
# ' 

我的方法似乎完全按照我所解釋的定義。我認爲第一個錯誤是一個範圍問題,因爲裸塊,但不是100%肯定。第二個錯誤我不確定,因爲如果我要創建一個Foofle類作爲Animal的孩子並且在其上調用speak,我不會得到1響應,而是得到預期的輸出。

有人能幫助我可能做錯什麼嗎?對於可能相關的軟件版本,我使用的是perl v5.16,Test :: More v0.98和Module :: Starter v1.58。

+4

一個很好的問題,很好的形成和包含適當的細節。歡迎來到SO –

回答

5

你已經非常正確地解釋了第一個錯誤的原因,並正確解決了它(指定了正確的軟件包名稱)。但你似乎錯過了一個簡單的事實:speak動物類的方法不returna $class goes...字符串 - 它返回它的打印結果(它是1),而不是!

看到,這個子程序:

sub speak { 
    my $class = shift; 
    print "a $class goes ", $class->sound, "!\n"; 
} 

...沒有一個明確的說法return。在這種情況下,返回是評估子程序的最新調用語句的結果 - 即評估print something的結果,其實際上是is1true,實際)。

這就是測試失敗的原因。你可以通過測試1(但我認爲這太微不足道了)或者改變方法本身來修復它,所以它會返回一個打印的字符串。例如:

sub speak { 
    my $class = shift; 
    my $statement = "a $class goes " . $class->sound . "!\n"; 
    print $statement; 
    return $statement; 
} 

......坦率地說,這兩種方法看起來有點......可疑。後者雖然明顯更完整,但實際上並不涵蓋該方法的所有功能:它測試該陳述是否正確或不是僅僅是,而不是它是否被打印。 )

+0

有趣,謝謝!我應該返回字符串而不是打印它。我必須仔細檢查,但我認爲這將成爲本書的勘誤。 – rsa

2

我想象你的代碼看起來是這樣的:

package SomeTest; # if omitted, it's like saying "package main" 
use Test::More; 
... 
{ 
    package Foofle; 
    is(something, something_else); 
} 

use Test::More語句將一些Test::More的功能導出到調用的命名空間,在這種情況下SomeTest(或main)。這意味着函數將用於符號main::ismain::okmain::done_testing來定義,等等

在與package Foofle開始塊,你現在在Foofle名字空間,所以現在的Perl將尋找對應於所述的功能符號Foofle::is。它不會找到一個,所以它會抱怨並退出。

一種解決方法是將Test::More也導入Foofle的命名空間。

{ 
    package Foofle; 
    use Test::More; 
    is(something, something_else); 
} 

,另一種是使用完全限定的方法名稱來調用is

{ 
    package Foofle; 
    Test::More::is(something, something_else); 
} 
4

你已經摸索出與您的來電is的問題是,你錯了在您打電話時包裹。完全確定你有做工作的功能名稱,並導入is到您的命名空間的封裝與測試說

use Test::More; 

地方。

你問題的其餘部分的答案在於你正在測試和你在做什麼之間的區別。什麼speak確實是是打印,但當您詢問is(speak, ...)時,您在詢問speak返回的內容,這與它打印的內容無關。這實際上是print的非常有用的返回值。

由於speak的目的是打印一個特定的字符串,所以speak的一個測試應該測試它確實打印了一個字符串並且它是正確的字符串。然而,爲了做這個測試,你需要一些方法來捕獲打印的內容。

其實有幾個方法可以做到這一點,利用IO::File迫使你指定的文件句柄其打印到猴子打補丁print替換成你的類,但下面的技術不需要對被測系統進行任何修改以提高其可測性。

select內置允許您更改print打印的位置。默認的輸出頻道是STDOUT,儘管你通常應該假裝你不知道那種事情。幸運的是,您還可以使用select來發現原始文件句柄,儘管您應該確保恢復默認文件句柄(畢竟,這是一個全局變量),即使您的測試由於某種原因而死亡。所以你需要管理異常。你需要一個文件句柄,你可以檢查內容並且不一定真的打印任何東西; IO::Scalar可以幫助那裏。

通過這種方法,你風能夠與

package AnimalTest; 
use IO::Scalar; 

use Test::More tests => 1; 
use Try::Tiny; 

{ 
    package Foofle; 
    use base qw(Animal); 

    sub sound { 'foof' } 
} 

{ 
    my $original_FH = select; 
    try { 
     my $result; 
     select IO::Scalar->new(\$result); 

     Foofle->speak(); 
     is(
      $result, "A Foofle goes foof!\n", 
      "An Animal subclass does the right thing" 
     ); 
    } catch { 
     die $_; 
    } finally { 
     select $original_FH; 
    }; 
} 

Try::Tiny是確保你做,如果speak恰好給Animal動脈瘤不亂扔垃圾,print被重定向到修改測試原代碼一個標量而不是實際打印到屏幕上,現在測試失敗的原因是正確的,即:字符串具有不匹配的大小寫。

你會注意到涉及到很多設置;這是因爲被測系統的可測試性並不是特別好,所以我們必須進行補償。在我自己的代碼中,這不是我選擇的方法,而是選擇使原始代碼更易於測試。然後進行測試,我通常使用TMOE猴子補丁(即覆蓋測試中的一種方法)。這種方法看起來更像是這樣的:

[動物:]

sub speak { 
    my $class = shift; 
    $class->print("a $class goes ", $class->sound, "!\n"); 
} 

sub print { 
    my $class = shift; 
    print @_; 
} 

[更新:]

{ 
    package Foofle; 
    use base qw(Animal); 

    sub sound { 'foof' } 

    sub print { 
     my ($self, @text) = @_; 

     return join '', @text; 
    } 

} 

is(
    Foofle->speak(), "A Foofle goes foof!\n", 
    "An Animal subclass does the right thing" 
); 

你會發現,這看起來更像是你的原代碼。主要區別在於,不是直接調用內置print,而是Animal調用$class->print,它依次調用內置的print。子類Foofle然後重寫print方法返回其參數而不是打印它們,這使測試代碼可以訪問已打印的內容。

這種方法比修改全局變量以找出打印內容要乾淨得多,但它確實有兩個缺點:它需要修改測試中的代碼以使其更易於測試,並且它從未真正測試過打印發生。它只是測試print是用正確的參數調用的。因此,動物::打印如此微不足道,以至於通過檢查顯然是正確的。

+0

由於我發現我想提出兩種選擇,所以這個答案比我想象的要長。 – darch