我该如何修改这段代码,以便使用它来迭代和测试错误列表



这只是我在错误中运行的一个测试的示例。我想编辑这个,这样我就可以循环浏览错误列表。我会把这些错误放入一个散列中,并创建一个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;

我只是不知道如何创建我的列表。我应该创建一个包含要测试的输入、处理和输出的列表吗。我有点困惑。

在聊天中进行了长时间的讨论后,我提出了以下方法来进行单元测试,并对实际代码进行了简单的重组,使事情变得更容易。

我所做的更改

我已经重新构造了代码从模板创建错误消息的方式,使其不使用template,因为从您之前的问题中可以清楚地看出,这有点过头了。

它现在使用具有类似Timeout after %s seconds的简单模式的sprintf。我特意在整个示例中使用了%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);

上下文是一个数组引用,其中包含构造时所需的键列表。

重构(简化)错误类

这节课包括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 : %snMore 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中错误模板的正确性。后来可能有人不小心弄混了一些东西,或者可能在消息中添加了一个新的占位符,但没有添加到上下文数组中。

下面的测试代码最好放在自己的文件中,并且只有在您完成某个功能时才能运行,但为了便于说明,这只是在上面的代码块之后继续,因此有两个一级subtests。

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

测试也是生产代码

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

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执行HEADHTTP请求,以查看这些URL是否可以访问。这还有一个好处,如果你在构建时运行它,它的作用有点像监控工具。

将其整合在一起

最后,这里是在没有prove的情况下运行时的TAP输出。

# 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

最新更新