【问题标题】:How would I modify this code so that I can use it to iterate and test through a list of errors?我将如何修改此代码,以便可以使用它来迭代和测试错误列表?
【发布时间】:2016-04-27 03:02:49
【问题描述】:

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

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

【问题讨论】:

  • 请说明问题所在。您是否在问如何构建单元测试以避免代码重复?您说的是process,从您之前的问题来看,我认为这是一种方法。为什么要进入单元测试?
  • 不抱歉,我只是想知道如何设置我的测试,这样我就可以使用 for 循环或类似的方法对多个错误运行测试,而不是测试一个错误.我不是有意将文字处理带入其中。我只是想我应该用data = [input => UNABLE_TO_PING_SWITCH_ERROR, output => "Could not ping switch 192.192.0.0 in 30 seconds];创建一个列表是否可以为每个错误做类似的事情,放入一个数据数组并循环遍历它
  • 你明白我想要做什么了吗?也许我走错路了?
  • 哦,真的。为什么常量让它变得棘手?

标签: perl unit-testing


【解决方案1】:

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

我所做的更改

我已经重组了代码从模板创建错误消息的方式,以不使用Template,因为从your previous question 中可以清楚地看出这有点矫枉过正。

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

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

错误库示例

第一个参数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 并使用它来检查构造函数是否可以处理正确的参数、丢失或多余的参数,访问器是否返回正确的内容,以及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 输出中的测试用例、运行测试所需的参数以及预期的最终输出。我只包含 message 以保持简短。

如果错误模板名称在 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

【讨论】:

  • 我们可以在您有空的时候开始讨论吗?我对代码有几个问题。我的功能测试工作正常,但我的正确性测试给出了一些错误。
  • @PaulRussell 进入旧版本
  • 接下来几天我大部分时间都在线。谢谢
猜你喜欢
  • 2021-07-25
  • 1970-01-01
  • 1970-01-01
  • 2016-07-20
  • 2019-10-28
  • 2020-11-12
  • 1970-01-01
  • 2021-10-08
  • 2021-05-29
相关资源
最近更新 更多