2016-04-25 76 views
0

这只是我对错误运行的一个测试的一个例子。我想编辑这个以便我可以遍历错误列表。我会把这些错误放入散列并创建一个for循环来遍历它们。我不确定它是如何完成的。 我将在下面显示测试和错误库。只需要一个小例子来让我走。如何修改此代码以便我可以使用它来迭代和测试错误列表?

测试文件:

use lib('./t/lib/'); 
use Test::More tests => 3; 
use ASC::Builder:Error; 
######################################################################################################### 
############## test for new() method in Error.pm - Test Case: HASH #################################### 
######################################################################################################### 



# error hash 
my $error_hash = UNABLE_TO_PING_SWITCH_ERROR; 

# error hash is passed into new and an error object is outputted 
my $error_in = ASC::Builder::Error->new($error_hash); 

# checks to see if the output object from new is an Error object 
isa_ok($error_in, 'ASC::Builder::Error'); 

# checking that object can call the message() method 
can_ok($error_in, 'message'); 


# checks to see if the output message matches the message contained in the error hash(correct) 
is($error_in->message(),($error_hash->{message}), 'Returns correct error message'); 

ErrorLibrary.pm

package ASC::Builder::ErrorLibrary; 

use strict; 
use warnings; 
use parent 'Exporter'; 

# list of export error messages 
our @EXPORT_OK = qw/ 

INCORRECT_CABLING_ERROR 
UPDATE_IMAGE_ERROR 
UNABLE_TO_PING_SWITCH_ERROR 
/; 

# error message list 

use constant { 
    # wiki link included as a variable in this example 
    INCORRECT_CABLING_ERROR => { 
     code => "INCORRECT_CABLING_ERROR", 
     errorNum => 561, 
     category => 'Cabling Error', 
     message => "ToR cabling is not correct at T1.The uplinks must be cabled to exactly one t1 device group", 
     tt => { template => 'disabled'}, 
     fatal => 1, 
     wiki_page =>'http://w.server-build.com/index.phpBuilder/ErrorCodes/INCORRECT_CABLING_ERROR', 
    }, 

    UPDATE_IMAGE_ERROR => { 
     code => "UPDATE_IMAGE_ERROR", 
     errorNum => 556, 
     category => 'Switch Error', 
     message => "Cannot determine switch model", 
     tt => { template => 'disabled'}, 
     fatal => 1, 
     wiki_page =>'http://www.server-build.com/index.php/NetMgmt/Builder/ErrorCodes/UPDATE_IMAGE_ERROR', 
    }, 

    UNABLE_TO_PING_SWITCH_ERROR => { 
     code => "UNABLE_TO_PING_SWITCH_ERROR", 
     errorNum => 727, 
     category => 'Switch Error', 
     message => "Could not ping switch [% switch_ip %] in [% timeout %] seconds.", 
     tt => {template => 'disabled'}, 
     fatal => 1, 
     wiki_page => 'http://www.server-build.com/index.php/Builder/ErrorCodes/UNABLE_TO_PING_SWITCH_ERROR', 
    }, 

    UNKNOWN_CLIENT_CERT_ID_ERROR => { 
     code => "UNKNOWN_CLIENT_CERT_ID_ERROR", 
     errorNum => 681, 
     category => 'Services Error', 
     message => "Unknown client certificate id: [% cert_id %]", 
     tt => { template => 'disabled'}, 
     fatal => 1, 
     wiki_page =>'http://www.server-build.com/index.php/Builder/ErrorCodes/UNKNOWN_CLIENT_CERT_ID_ERROR', 
    }, 


# add errors to this library  
}; 


1; 

我只是不知道如何创建我的列表。我应该创建一个包含输入,过程和输出的列表来测试。我有点困惑。

+0

请说明问题所在。你在问如何构建单元测试以避免代码重复?你正在谈论'流程',从你以前的问题中我相信这是一种方法。为什么会进入单元测试? – simbabque

+0

没有抱歉,我只是想知道如何设置我的测试,以便能够测试一个错误,我可以使用for循环或类似的方式运行多个错误的测试。我并不是故意将这个词汇加入其中。我只是认为我应该用'data = [input => UNABLE_TO_PING_SWITCH_ERROR,output =>“无法在30秒内ping开关192.192.0.0]创建一个列表;''对于每个错误,是否可以这样做?进入一个数据数组并通过它循环 –

+0

你会得到我想要做的吗?也许我会错误的方式吗? –

回答

1

经过长时间的聊天讨论,我提出了以下方法来进行单元测试,以及轻松重构实际代码以使事情变得更容易。

变化我做

我已经重构的代码从模板创建错误消息不使用Template的方式,因为它是清楚your previous question,这是一个有点矫枉过正。

它现在使用sprintfTimeout after %s seconds这样的简单模式。我在整个例子中都使用了%s,因为从来没有任何类型的检查,但它当然可以添加。此消息的参数作为从第二个参数开始的键/值对列表传递给构造函数。

my $e = Error->new(CONSTANT, foo => 'bar'); 

的例子ErrorLibrary

的第一个参数CONSTANT仍然是你的错误库。我已经包括了以下简化的例子。

package ErrorList; 
use strict; 
use warnings; 
use parent 'Exporter'; 
use constant { 
    ERROR_WIFI_CABLE_TOO_SHORT => { 
     category => 'Layer 1', 
     template => 'A WiFi cable of %s meters is too short.', 
     context => [qw(length)], 
     fatal  => 1, 
     wiki_page => 'http://example.org', 
    }, 
    ERROR_CABLE_HAS_WRONG_COLOR => { 
     category => 'Layer 1', 
     template => 'You cannot connect to %s using a %s cable.', 
     context => [qw(router color)], 
     fatal  => 1, 
     wiki_page => 'http://example.org', 
    }, 
    ERROR_I_AM_A_TEAPOT => { 
     category => 'Layer 3', 
     template => 'The device at %s is a teapot.', 
     context => [qw(ip)], 
     fatal  => 0, 
     wiki_page => 'http://example.org', 
    }, 
}; 

our @EXPORT = qw(
    ERROR_WIFI_CABLE_TOO_SHORT 
    ERROR_CABLE_HAS_WRONG_COLOR 
    ERROR_I_AM_A_TEAPOT 
); 

our @EXPORT_OK = qw(ERROR_WIFI_CABLE_TOO_SHORT); 

上下文是与预计在施工该密钥的列表的数组引用。

的重构(简体)Error类

这一类包括POD来解释它做什么。重要的方法是构造函数messagestringify

package Error; 
use strict; 
use warnings; 

=head1 NAME 

Error - A handy error class 

=head1 SYNOPSIS 

use Error; 
use ErrorList 'ERROR_WIFI_CABLE_TOO_SHORT'; 

    my $e = Error->new(
     ERROR_WIFI_CABLE_TOO_SHORT, 
     timeout => 30, 
     switch_ip => '127.0.0.1' 
    ); 
    die $e->stringify; 

=head1 DESCRIPTION 

This class can create objects from a template and stringify them into a 
log-compatible pattern. It makes sense to use it together 
with L<ErrorList>. 


=head1 METHODS 

=head2 new($error, %args) 

The constructor takes the error definition and a list of key/value pairs 
with context information as its arguments. 

... 

=cut 

sub new { 
    my ($class, $error, %args) = @_; 

    # initialize with the error data 
    my $self = $error; 

    # check required arguments... 
    foreach my $key (@{ $self->{context} }) { 
     die "$key is required" unless exists $args{$key}; 

     # ... and take the ones we need 
     $self->{args}->{$key} = $args{$key}; # this could have a setter 
    } 

    return bless $self, $class; 
} 

=head2 category 

This is the accessor for the category. 

=cut 

sub category { 
    return $_[0]->{category}; 
} 

=head2 template 

This is the accessor for the template. 

=cut 

sub template { 
    return $_[0]->{template}; 
} 

=head2 fatal 

This is the accessor for whether the error is fatal. 

=cut 

sub is_fatal { 
    return $_[0]->{fatal}; 
} 

=head2 wiki_page 

This is the accessor for the wiki_page. 

=cut 

sub wiki_page { 
    return $_[0]->{wiki_page}; 
} 

=head2 context 

This is the accessor for the context. The context is an array ref 
of hash key names that are required as context arguments at construction. 

=cut 

sub context { 
    return $_[0]->{context}; 
} 

=head2 category 

This is the accessor for the args. The args are a hash ref of context 
arguments that are passed in as a list at construction. 

=cut 

sub args { 
    return $_[0]->{args}; 
} 

=head2 message 

Builds the message string from the template. 

=cut 

sub message { 
    my ($self) = @_; 

    return sprintf $self->template, 
     map { $self->args->{$_} } @{ $self->context }; 
} 

=head2 stringify 

Stringifies the error to a log message, including the message, 
category and wiki_page. 

=cut 

sub stringify { 
    my ($self) = @_; 

    return sprintf qq{%s : %s\nMore info: %s}, $self->category, 
     $self->message, $self->wiki_page; 
} 

=head1 AUTHOR 

simbabque (some guy on StackOverflow) 

=cut 

本单位实际测试

我们测试,它的行为和数据进行区分是很重要的。 行为包括代码中定义的所有访问器,以及更多有趣的子项,如newmessagestringify

我为这个例子创建的测试文件的第一部分包括这些。它会创建一个假错误结构$example_error,并使用它来检查构造函数是否可以处理正确的参数,缺失或超出的参数,访问器返回正确的内容,并且messagestringify都会创建正确的内容。

请记住,这些测试主要是更改代码时的安全网(尤其是在几个月后)。如果你不小心改变了错误的地方,测试将会失败。

package main; # something like 01_foo.t 
use strict; 
use warnings; 
use Test::More; 
use Test::Exception; 
use LWP::Simple 'head'; 

subtest 'Functionality of Error' => sub { 
    my $example_error = { 
     category => 'Connection Error', 
     template => 'Could not ping switch %s in %s seconds.', 
     context => [qw(switch_ip timeout)], 
     fatal  => 1, 
     wiki_page => 'http://example.org', 
    }; 

    # happy case 
    { 
     my $e = Error->new(
      $example_error, 
      timeout => 30, 
      switch_ip => '127.0.0.1' 
     ); 
     isa_ok $e, 'Error'; 

     can_ok $e, 'category'; 
     is $e->category, 'Connection Error', 
      q{... and it returns the correct value}; 

     can_ok $e, 'template'; 
     is $e->template, 'Could not ping switch %s in %s seconds.', 
      q{... and it returns the correct values}; 

     can_ok $e, 'context'; 
     is_deeply $e->context, [ 'switch_ip', 'timeout' ], 
      q{... and it returns the correct values}; 

     can_ok $e, 'is_fatal'; 
     ok $e->is_fatal, q{... and it returns the correct values}; 

     can_ok $e, 'message'; 
     is $e->message, 'Could not ping switch 127.0.0.1 in 30 seconds.', 
      q{... and the message is correct}; 

     can_ok $e, 'stringify'; 
     is $e->stringify, 
      "Connection Error : Could not ping switch 127.0.0.1 in 30 seconds.\n" 
      . "More info: http://example.org", 
      q{... and stringify contains the right message}; 
    } 

    # not enough arguments 
    throws_ok(sub { Error->new($example_error, timeout => 1) }, 
     qr/switch_ip/, q{Creating without switch_ip dies}); 

    # too many arguments 
    lives_ok(
     sub { 
      Error->new(
       $example_error, 
       timeout => 1, 
       switch_ip => 2, 
       foo  => 3 
      ); 
     }, 
     q{Creating with too many arguments lives} 
    ); 

}; 

有一些特定的测试案例丢失。如果您使用像Devel::Cover这样的度量工具,值得注意的是全覆盖并不意味着涵盖所有可能的情况。

测试您的错误数据质量

现在的第二部分,是值得覆盖在这个例子是在ErrorLibrary错误模板的正确性。有人可能会在以后意外混淆某些东西,或者可能会在消息中添加新的占位符,但不会添加到上下文数组中。

下面的测试代码理想情况下将放置在它自己的文件中,并且只有在完成某个功能时才能运行,但出于说明的目的,这只是在上述代码块之后继续执行,因此两个第一级subtest

您问题的主要部分是关于测试用例的列表。我认为这是非常重要的。你希望你的测试代码干净,容易阅读,甚至更容易维护。测试经常作为文档加倍,没有什么比更改代码更烦人,然后试图弄清楚测试如何工作,以便更新它们。所以永远记住这一点:

测试也是生产代码!

现在让我们来看看这些错误的测试。

subtest 'Correctness of ErrorList' => sub { 

    # these test cases contain all the errors from ErrorList 
    my @test_cases = (
     { 
      name => 'ERROR_WIFI_CABLE_TOO_SHORT', 
      args => { 
       length => 2, 
      }, 
      message => 'A WiFi cable of 2 meters is too short.', 
     }, 
     { 
      name => 'ERROR_CABLE_HAS_WRONG_COLOR', 
      args => { 
       router => 'foo', 
       color => 'red', 
      }, 
      message => 'You cannot connect to foo using a red cable.', 
     }, 
     { 
      name => 'ERROR_I_AM_A_TEAPOT', 
      args => { 
       ip => '127.0.0.1', 
      }, 
      message => 'The device at 127.0.0.1 is a teapot.', 
     }, 
    ); 

    # use_ok 'ErrorList'; # only use this line if you have files! 
    ErrorList->import; # because we don't have a file ErrorList.pm 
          # in the file system 
    pass 'ErrorList used correctly'; # remove if you have files 

    foreach my $t (@test_cases) { 
     subtest $t->{name} => sub { 

      # because we need to use a variable to get to a constant 
      no strict 'refs'; 

      # create the Error object from the test data 
      # will also fail if the name was not exported by ErrorList 
      my $e; 
      lives_ok(
       sub { $e = Error->new(&{ $t->{name} }, %{ $t->{args} }) }, 
       q{Error can be created}); 

      # and see if it has the right values 
      is $e->message, $t->{message}, 
       q{... and the error message is correct}; 

      # use LWP::Simple to check if the wiki page link is not broken 
      ok head($e->wiki_page), q{... and the wiki page is reachable}; 
     }; 
    } 
}; 

done_testing; 

它基本上有一个测试用例数组,其中每个可能的错误常量由ErrorLibrary导出。它具有名称,用于加载正确的错误,并在TAP输出中标识测试用例,运行测试所需的参数以及预期的最终输出。我只包含消息以保持简短。

如果在ErrorLibrary(或删除)中修改错误模板名称而未更改文本,围绕对象实例化的lives_ok将失败,因为该名称未导出。这是一个很好的补充。

但是,如果在没有测试用例的情况下添加新的错误,它将不会被捕获。一种方法是查看名称空间main中的符号表,但这对于此答案的范围来说有点太高级了。

它还会用LWP::Simple对每个wiki URL执行HEAD HTTP请求以查看它们是否可到达。这也有很好的好处,如果你在构建时运行它,它就像一个监视工具。

把它放在一起

最后,这里是TAP输出,当没有prove运行。

# Subtest: Functionality of Error 
    ok 1 - An object of class 'Error' isa 'Error' 
    ok 2 - Error->can('category') 
    ok 3 - ... and it returns the correct value 
    ok 4 - Error->can('template') 
    ok 5 - ... and it returns the correct values 
    ok 6 - Error->can('context') 
    ok 7 - ... and it returns the correct values 
    ok 8 - Error->can('is_fatal') 
    ok 9 - ... and it returns the correct values 
    ok 10 - Error->can('message') 
    ok 11 - ... and the message is correct 
    ok 12 - Error->can('stringify') 
    ok 13 - ... and stringify contains the right message 
    ok 14 - Creating without switch_ip dies 
    ok 15 - Creating with too many arguments lives 
    1..15 
ok 1 - Functionality of Error 
    # Subtest: Correctness of ErrorList 
    ok 1 - ErrorList used correctly 
     # Subtest: ERROR_WIFI_CABLE_TOO_SHORT 
     ok 1 - Error can be created 
     ok 2 - ... and the error message is correct 
     ok 3 - ... and the wiki page is reachable 
     1..3 
    ok 2 - ERROR_WIFI_CABLE_TOO_SHORT 
     # Subtest: ERROR_CABLE_HAS_WRONG_COLOR 
     ok 1 - Error can be created 
     ok 2 - ... and the error message is correct 
     ok 3 - ... and the wiki page is reachable 
     1..3 
    ok 3 - ERROR_CABLE_HAS_WRONG_COLOR 
     # Subtest: ERROR_I_AM_A_TEAPOT 
     ok 1 - Error can be created 
     ok 2 - ... and the error message is correct 
     ok 3 - ... and the wiki page is reachable 
     1..3 
    ok 4 - ERROR_I_AM_A_TEAPOT 
    1..4 
ok 2 - Correctness of ErrorList 
1..2 
+0

我们可以在任何时候开放讨论吗?我有几个关于代码的问题。我的功能测试工作正常,但我的正确性测试给出了一些错误。 –

+1

@PaulRussell进入旧的测试 – simbabque

+0

我将在接下来的几天大部分时间在线。谢谢 –

相关问题