
关于
使用 Test2::V0、Test::More、prove 运行器、模拟、Devel::Cover 覆盖率和 TDD 工作流的 Perl 测试模式。
name: perl-testing description: 使用 Test2::V0、Test::More、prove 运行器、模拟、Devel::Cover 覆盖率和 TDD 方法论的 Perl 测试模式。 origin: ECC
Perl 测试模式
使用 Test2::V0、Test::More、prove 和 TDD 方法论的 Perl 应用综合测试策略。
何时激活
- 编写新的 Perl 代码时(遵循 TDD:红、绿、重构)
- 为 Perl 模块或应用设计测试套件时
- 审查 Perl 测试覆盖率时
- 搭建 Perl 测试基础设施时
- 从 Test::More 迁移到 Test2::V0 时
- 调试失败的 Perl 测试时
TDD 工作流
始终遵循红-绿-重构循环。
# 第1步:RED — 编写一个失败的测试
# t/unit/calculator.t
use v5.36;
use Test2::V0;
use lib 'lib';
use Calculator;
subtest 'addition' => sub {
my $calc = Calculator->new;
is($calc->add(2, 3), 5, 'adds two numbers');
is($calc->add(-1, 1), 0, 'handles negatives');
};
done_testing;
# 第2步:GREEN — 编写最小实现
# lib/Calculator.pm
package Calculator;
use v5.36;
use Moo;
sub add($self, $a, $b) {
return $a + $b;
}
1;
# 第3步:REFACTOR — 在测试保持通过的前提下改进代码
# 运行: prove -lv t/unit/calculator.t
Test::More 基础
标准 Perl 测试模块——广泛使用,随核心发行版附带。
基本断言
use v5.36;
use Test::More;
# 预先声明计划或使用 done_testing
# plan tests => 5; # 固定计划(可选)
# 相等性
is($result, 42, 'returns correct value');
isnt($result, 0, 'not zero');
# 布尔值
ok($user->is_active, 'user is active');
ok(!$user->is_banned, 'user is not banned');
# 深度比较
is_deeply(
$got,
{ name => 'Alice', roles => ['admin'] },
'returns expected structure'
);
# 模式匹配
like($error, qr/not found/i, 'error mentions not found');
unlike($output, qr/password/, 'output hides password');
# 类型检查
isa_ok($obj, 'MyApp::User');
can_ok($obj, 'save', 'delete');
done_testing;
SKIP 和 TODO
use v5.36;
use Test::More;
# 条件跳过测试
SKIP: {
skip 'No database configured', 2 unless $ENV{TEST_DB};
my $db = connect_db();
ok($db->ping, 'database is reachable');
is($db->version, '15', 'correct PostgreSQL version');
}
# 标记预期失败
TODO: {
local $TODO = 'Caching not yet implemented';
is($cache->get('key'), 'value', 'cache returns value');
}
done_testing;
Test2::V0 现代框架
Test2::V0 是 Test::More 的现代替代品——更丰富的断言、更好的诊断输出、可扩展。
为什么选择 Test2?
- 使用 hash/array 构建器实现更优的深度比较
- 失败时提供更好的诊断输出
- 子测试具有更清晰的作用域
- 通过 Test2::Tools::* 插件可扩展
- 向后兼容 Test::More 测试
使用构建器进行深度比较
use v5.36;
use Test2::V0;
# Hash 构建器 — 检查部分结构
is(
$user->to_hash,
hash {
field name => 'Alice';
field email => match(qr/@example.com$/);
field age => validator(sub { $_ >= 18 });
# 忽略其他字段
etc();
},
'user has expected fields'
);
# Array 构建器
is(
$result,
array {
item 'first';
item match(qr/^second/);
item DNE(); # Does Not Exist — 验证没有多余元素
},
'result matches expected list'
);
# Bag — 顺序无关的比较
is(
$tags,
bag {
item 'perl';
item 'testing';
item 'tdd';
},
'has all required tags regardless of order'
);
子测试
use v5.36;
use Test2::V0;
subtest 'User creation' => sub {
my $user = User->new(name => 'Alice', email => 'alice@example.com');
ok($user, 'user object created');
is($user->name, 'Alice', 'name is set');
is($user->email, 'alice@example.com', 'email is set');
};
subtest 'User validation' => sub {
my $warnings = warns {
User->new(name => '', email => 'bad');
};
ok($warnings, 'warns on invalid data');
};
done_testing;
Test2 异常测试
use v5.36;
use Test2::V0;
# 测试代码是否抛出异常
like(
dies { divide(10, 0) },
qr/Division by zero/,
'dies on division by zero'
);
# 测试代码是否正常执行
ok(lives { divide(10, 2) }, 'division succeeds') or note($@);
# 组合模式
subtest 'error handling' => sub {
ok(lives { parse_config('valid.json') }, 'valid config parses');
like(
dies { parse_config('missing.json') },
qr/Cannot open/,
'missing file dies with message'
);
};
done_testing;
测试组织与 prove
目录结构
t/
├── 00-load.t # 验证模块可编译
├── 01-basic.t # 核心功能
├── unit/
│ ├── config.t # 按模块划分的单元测试
│ ├── user.t
│ └── util.t
├── integration/
│ ├── database.t
│ └── api.t
├── lib/
│ └── TestHelper.pm # 共享测试工具
└── fixtures/
├── config.json
兼容工具
Claude CodeCursor
标签
测试

