我如何修改此代码以便我可以使用它来迭代并测试错误列表?

时间:2016-04-25 12:03:49

标签: perl unit-testing

这只是我在错误上运行的一个测试的示例。我想编辑它,以便我可以遍历错误列表。我会将这些错误放入哈希并创建一个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;

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

1 个答案:

答案 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);

context 是一个数组引用,其中包含构造时预期的键列表。

重构(简化)错误类

这个课程包括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,并使用它来检查构造函数是否可以处理正确的参数,缺少或多余的参数,访问者返回正确的东西,以及message和{{1两者都创建了正确的内容。

请记住,在更改代码时(特别是几个月后),这些测试主要是安全网。如果您在错误的地方意外更改了某些内容,则测试将失败。

stringify

缺少一些特定的测试用例。如果您使用Devel::Cover之类的指标工具,则值得注意的是,完全覆盖并不意味着涵盖了所有可能的情况。

测试您的错误数据质量

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

理想情况下,以下测试代码将放在自己的文件中,并且只有在完成某个功能时才会运行,但为了说明的目的,这只是在上面的代码块之后继续,因此两个第一级{{1 }}第

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

测试也是生产代码!

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

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}
    );

};

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

如果在不更改文本的情况下在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; 命名空间中的符号表,但这对于这个答案的范围来说有点太高级了。

它还使用LWP::Simple对每个wiki URL执行lives_ok HTTP请求,以查看是否可以访问这些请求。这也有很好的好处,如果你在构建它时运行它有点像监视工具。

将所有内容整合在一起

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

HEAD