Communitygithub.com

perl-testing

Perl 测试模式:使用 Test2::V0、Test::More、prove 运行器、模拟、Devel::Cover 覆盖率以及 TDD 方法。在编写 Test2::V0 或 Test::More 的 Perl 测试或测量覆盖率时使用。

perl-testing 是什么?

该技能提供了在 Perl 应用程序中使用现代测试框架(如 Test2::V0 和 Test::More)进行测试的全面模式与方法。它遵循红-绿-重构的 TDD 工作流,并涵盖了从基础断言到深度比较、子测试、异常测试的完整测试编写方法。技能还详细介绍了如何使用 prove 运行器组织测试、通过 Test::MockModule 进行模拟以及使用 Devel::Cover 测量代码覆盖率。该技能支持所有主要平台(Claude Code、Cursor、Codex 等),并提供了丰富的代码示例、项目目录结构建议、CI 集成命令以及避免常见陷阱的最佳实践,适合任何使用 Perl 的开发者。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/perl-testing

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

Perl 测试模式

使用 Test2::V0、Test::More、prove 和 TDD 方法对 Perl 应用程序进行全面测试的策略。

何时激活

  • 编写新的 Perl 代码(遵循 TDD:红、绿、重构)
  • 为 Perl 模块或应用程序设计测试套件
  • 审查 Perl 测试覆盖率
  • 设置 Perl 测试基础设施
  • 将测试从 Test::More 迁移到 Test2::V0
  • 调试失败的 Perl 测试

TDD 工作流

始终遵循红-绿-重构循环。

# 步骤 1:红 — 编写一个失败的测试
# 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:绿 — 编写最小的实现
# lib/Calculator.pm
package Calculator;
use v5.36;
use Moo;

sub add($self, $a, $b) {
    return $a + $b;
}

1;

# 步骤 3:重构 — 在测试保持绿色的情况下改进
# 运行: 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?

  • 使用哈希/数组构造器进行卓越的深度比较
  • 失败时提供更好的诊断输出
  • 子测试具有更清晰的作用域
  • 可通过 Test2::Tools::* 插件扩展
  • 向后兼容 Test::More 测试

使用构造器进行深度比较

use v5.36;
use Test2::V0;

# 哈希构造器 — 检查部分结构
is(
    $user->to_hash,
    hash {
        field name  => 'Alice';
        field email => match(qr/\@example\.com$/);
        field age   => validator(sub { $_ >= 18 });
        # 忽略其他字段
        etc();
    },
    'user has expected fields'
);

# 数组构造器
is(
    $result,
    array {
        item 'first';
        item match(qr/^second/);
        item DNE();  # 不存在 — 验证没有额外元素
    },
    '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 => '[email protected]');
    ok($user, 'user object created');
    is($user->name, 'Alice', 'name is set');
    is($user->email, '[email protected]', '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        # 测试数据文件
    └── users.csv

prove 命令

# 运行所有测试
prove -l t/

# 详细输出
prove -lv t/

# 运行特定测试
prove -lv t/unit/user.t

# 递归搜索
prove -lr t/

# 并行执行(8 个作业)
prove -lr -j8 t/

# 仅运行上次失败的测试
prove -l --state=failed t/

# 彩色输出带计时器
prove -l --color --timer t/

# 用于 CI 的 TAP 输出
prove -l --formatter TAP::Formatter::JUnit t/ > results.xml

.proverc 配置

-l
--color
--timer
-r
-j4
--state=save

装置和设置/拆卸

子测试隔离

use v5.36;
use Test2::V0;
use File::Temp qw(tempdir);
use Path::Tiny;

subtest 'file processing' => sub {
    # 设置
    my $dir = tempdir(CLEANUP => 1);
    my $file = path($dir, 'input.txt');
    $file->spew_utf8("line1\nline2\nline3\n");

    # 测试
    my $result = process_file("$file");
    is($result->{line_count}, 3, 'counts lines');

    # 拆卸自动发生 (CLEANUP => 1)
};

共享测试助手

将可重用的助手放在 t/lib/TestHelper.pm 中,并使用 use lib 't/lib' 加载。通过 Exporter 导出工厂函数,如 create_test_db()、create_temp_dir() 和 fixture_path()。

模拟

Test::MockModule

use v5.36;
use Test2::V0;
use Test::MockModule;

subtest 'mock external API' => sub {
    my $mock = Test::MockModule->new('MyApp::API');

    # 好:模拟返回受控数据
    $mock->mock(fetch_user => sub ($self, $id) {
        return { id => $id, name => 'Mock User', email => '[email protected]' };
    });

    my $api = MyApp::API->new;
    my $user = $api->fetch_user(42);
    is($user->{name}, 'Mock User', 'returns mocked user');

    # 验证调用次数
    my $call_count = 0;
    $mock->mock(fetch_user => sub { $call_count++; return {} });
    $api->fetch_user(1);
    $api->fetch_user(2);
    is($call_count, 2, 'fetch_user called twice');

    # 当 $mock 超出作用域时,模拟自动恢复
};

# 坏:猴子补丁而不恢复
# *MyApp::API::fetch_user = sub { ... };  # 绝不 — 跨测试泄漏

对于轻量级模拟对象,请使用 Test::MockObject 创建可注入的测试替身,通过 ->mock() 模拟方法,并使用 ->called_ok() 验证调用。

使用 Devel::Cover 进行覆盖率

运行覆盖率

# 基本覆盖率报告
cover -test

# 或按步骤进行
perl -MDevel::Cover -Ilib t/unit/user.t
cover

# HTML 报告
cover -report html
open cover_db/coverage.html

# 特定阈值
cover -test -report text | grep 'Total'

# 对 CI 友好:低于阈值时失败
cover -test && cover -report text -select '^lib/' \
  | perl -ne 'if (/Total.*?(\d+\.\d+)/) { exit 1 if $1 < 80 }'

集成测试

对数据库测试使用内存 SQLite,对 API 测试使用 Mock HTTP::Tiny。

use v5.36;
use Test2::V0;
use DBI;

subtest 'database integration' => sub {
    my $dbh = DBI->connect('dbi:SQLite:dbname=:memory:', '', '', {
        RaiseError => 1,
    });
    $dbh->do('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');

    $dbh->prepare('INSERT INTO users (name) VALUES (?)')->execute('Alice');
    my $row = $dbh->selectrow_hashref('SELECT * FROM users WHERE name = ?', undef, 'Alice');
    is($row->{name}, 'Alice', 'inserted and retrieved user');
};

done_testing;

最佳实践

要做

  • 遵循 TDD:先编写测试,后实现(红-绿-重构)
  • 使用 Test2::V0:现代断言,更好的诊断
  • 使用子测试:分组相关断言,隔离状态
  • 模拟外部依赖:网络、数据库、文件系统
  • 使用 prove -l:始终包含 lib/ 到 @INC
  • 清晰命名测试:'user login with invalid password fails'
  • 测试边界情况:空字符串、undef、零、边界值
  • 目标是 80%+ 覆盖率:专注于业务逻辑路径
  • 保持测试快速:模拟 I/O,使用内存数据库

不要做

  • 不要测试实现:测试行为与输出,而非内部细节
  • 不要在子测试间共享状态:每个子测试应独立
  • 不要省略 done_testing:确保所有计划的测试都运行了
  • 不要过度模拟:仅在边界处模拟,而非被测试代码
  • 不要在新项目中使用 Test::More:优先选择 Test2::V0
  • 不要忽略测试失败:所有测试必须在合并前通过
  • 不要测试 CPAN 模块:信任库能正确工作
  • 不要编写脆弱测试:避免过于具体的字符串匹配

快速参考

任务命令 / 模式
运行所有测试prove -lr t/
详细运行一个测试prove -lv t/unit/user.t
并行测试运行prove -lr -j8 t/
覆盖率报告cover -test && cover -report html
测试相等性is($got, $expected, 'label')
深度比较is($got, hash { field k => 'v'; etc() }, 'label')
测试异常like(dies { ... }, qr/msg/, 'label')
测试无异常ok(lives { ... }, 'label')
模拟方法Test::MockModule->new('Pkg')->mock(m => sub { ... })
跳过测试SKIP: { skip 'reason', $count unless $cond; ... }
TODO 测试TODO: { local $TODO = 'reason'; ... }

常见陷阱

忘记 done_testing

# 坏:测试文件运行但不验证所有测试已执行
use Test2::V0;
is(1, 1, 'works');
# 缺少 done_testing — 如果测试代码被跳过,则出现静默错误

# 好:始终用 done_testing 结束
use Test2::V0;
is(1, 1, 'works');
done_testing;

缺少 -l 标志

# 坏:lib/ 中的模块未找到
prove t/unit/user.t
# Can't locate MyApp/User.pm in @INC

# 好:将 lib/ 包含到 @INC 中
prove -l t/unit/user.t

过度模拟

模拟依赖,而不是被测试代码。如果你的测试只验证模拟返回了你告诉它返回的内容,那它什么也没测试。

测试污染

在子测试中使用 my 变量 — 永远不要使用 our — 以防止状态在测试之间泄漏。

记住:测试是你的安全网。保持它们快速、专注且独立。对新项目使用 Test2::V0,使用 prove 运行测试,使用 Devel::Cover 进行问责。

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

相关技能