如何在不创建测试类的情况下测试Perl角色
最近我一直在开发一个游戏引擎,它使用组合模式来处理演员。我使用Role::Tiny来创建角色。Role::Tiny非常方便,因为它允许你使用角色与原生OO Perl,而不必承诺使用像Moose这样的完整对象系统。一个典型的角色看起来像这样
package March::Attribute::Id;
use 5.020;
use Role::Tiny;
use feature 'signatures';
no warnings 'experimental';
sub id ($self)
{
$self->{id};
}
1;
这个角色所做的只是返回消费类的id属性(是的,我在整个过程中都使用了签名)。我想为这个角色编写单元测试,但我不想创建一个测试类来测试这个角色。那么如何从一个没有构造函数的包中构造一个对象呢?答案是使用你的测试文件中的bless
use strict;
use warnings;
use Test::More;
my $self = bless { id => 5 }, 'March::Attribute::Id';
BEGIN { use_ok 'March::Attribute::Id' }
is $self->id, 5, 'id()';
done_testing();
此代码通过将包名赋予角色来创建一个名为$self
的对象。它为id属性添加了一个键值对,然后测试角色的id方法是否返回正确的id值。我可以使用prove
执行测试
$ prove -vl t/Attribute/Id.t
t/Attribute/Id.t ..
ok 1 - use March::Attribute::Id;
ok 2 - id()
1..2
ok
All tests successful.
Files=1, Tests=2, 0 wallclock secs ( 0.02 usr 0.00 sys + 0.02 cusr 0.00 csys = 0.04 CPU)
Result: PASS
这是我想要测试的另一个角色
package March::Attribute::Direction;
use 5.020;
use Role::Tiny;
use feature 'signatures';
no warnings 'experimental';
use March::Game;
use March::Msg;
requires 'id';
sub direction ($self, $new_direction = 0)
{
if ($new_direction && $new_direction->isa('Math::Shape::Vector'))
{
$self->{direction} = $new_direction;
# publish direction to game queue
March::Game->publish(
March::Msg->new(__PACKAGE__, $self->id, $new_direction)
);
}
$self->{direction};
}
1;
这个角色获取和设置消费类的方向向量。测试这个角色的挑战在于它需要消费类实现一个id
方法。Role::Tiny的requires
函数是一个确保消费类满足角色要求的绝佳方式。但我们如何在不创建具有id
子程序的实时类的情况下进行测试呢?我做的事情是在测试文件中声明所需的子程序
use strict;
use warnings;
use Test::More;
use Math::Shape::Vector;
# create an object
my $self = bless { direction => Math::Shape::Vector->new(1, 2)
}, 'March::Attribute::Direction';
# add required sub
sub March::Attribute::Direction::id { 107 };
BEGIN { use_ok 'March::Attribute::Direction' }
is $self->direction->{x}, 1, 'Check direction x is 1';
is $self->direction->{y}, 2, 'Check direction y is 2';
ok $self->direction( Math::Shape::Vector->new(1, 0) ),
'Update direction to new vector';
is $self->direction->{x}, 1, 'Check direction x is still 1';
is $self->direction->{y}, 0, 'Check direction y is now 0';
done_testing();
神奇的行是sub March::Attribute::Direction::id { 107 };
,它将子程序添加到我要测试的角色中(它只返回值107)。现在我可以再次使用prove
测试direction
方法
$ prove -lv t/Attribute/Direction.t
t/Attribute/Direction.t ..
ok 1 - use March::Attribute::Direction;
ok 2 - Check direction
ok 3 - Check direction
ok 4 - Update direction to new vector
ok 5 - Check direction
ok 6 - Check direction
1..6
ok
All tests successful.
Files=1, Tests=6, 0 wallclock secs ( 0.02 usr 0.00 sys + 0.08 cusr 0.00 csys = 0.10 CPU)
Result: PASS
并非全是好事
我遇到的一个缺点可以用以下角色和测试文件来看到
package Data::Inspector;
use Role::Tiny;
sub inspect_data
{
my ($self, $data);
Data::Dumper->Dump(['Inspecting:', $data]);
}
1;
这个角色有一个名为inspect_data
的方法,它只是返回传递给它的任何数据引用的转储。这是测试文件
use Test::More;
use Data::Dumper;
my $self = bless {}, 'Data::Inspector';
BEGIN { use_ok 'Data::Inspector' }
ok $self->inspect_data({ test => 'data' });
done_testing();
和以前一样,我在测试文件中赋予角色祝福,然后继续测试inspect_data
方法。这个测试文件运行,所有测试都通过。你能在这里发现问题吗?注意,Data::Inspector角色使用Data::Dumper的 Dump
方法,但它没有加载Data::Dumper模块,测试文件有!这是一个问题,因为当Data::Inspector角色在其他实时代码中使用时,如果它找不到在内存中加载了Data::Dumper,它就会崩溃并燃烧。
结论
通过这个项目,我打算创建很多简单的角色,因此这种方法提供了一种轻量级的方法,可以在测试文件中测试角色,而无需为每个角色创建测试类。
我真的喜欢Role::Tiny。它很灵活:你可以创建类似特性的最小化行为,或者更进一步,创建混合(修改状态的角色)。它有诸如自动启用strict和warnings、方法修饰符和良好的文档等不错的功能。Role::Basic是另一个支持特性(通过设计)的轻量级角色模块。我怀疑随着游戏引擎的开发进一步深入,我会不会后悔使用混合方法。
这篇文章最初发布在PerlTricks.com。
标签
反馈
这篇文章有什么问题吗?请通过在GitHub上打开一个issue或pull request来帮助我们。