Communitygithub.com

Perl测试模式

使用Test2::V0、Test::More、prove运行器、模拟、Devel::Cover覆盖率以及TDD方法论编写Perl测试。适用于编写基于Test2::V0或Test::More的Perl测试,或测量覆盖率的场景。

Perl测试模式 是什么?

此技能为使用Perl进行测试开发的AI代理提供了全面的测试模式指导。它涵盖了从经典的Test::More到现代化的Test2::V0框架,详细介绍了断言方法、子测试、异常测试以及测试组织。技能强调TDD(红-绿-重构)工作流,并提供了完整的示例代码。此外,它还包括使用Devel::Cover进行覆盖率分析、使用Test::MockModule进行模拟,以及使用prove运行器进行测试执行和并行处理的实践。该技能适用于任何编写Perl代码的AI辅助环境(如Claude Code、Cursor等),帮助代理生成更可靠、可维护的测试代码。通过遵循这些模式,开发者可以确保测试的独立性、可读性和有效性,支持从单元测试到集成测试的全面质量保障。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/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步: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?

  • 使用哈希/数组构建器进行卓越的深度比较
  • 失败时提供更好的诊断输出
  • 通过更清晰的作用域使用子测试
  • 通过 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 => '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        # 测试数据文件
    └── 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

固定装置与 setup/teardown

子测试隔离

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 '模拟外部 API' => sub {
    my $mock = Test::MockModule->new('MyApp::API');

    # 好:Mock 返回受控数据
    $mock->mock(fetch_user => sub ($self, $id) {
        return { id => $id, name => 'Mock User', email => 'mock\@test.com' };
    });

    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 超出作用域时,Mock 会自动恢复
};

# 坏:没有恢复的猴子补丁
# *MyApp::API::fetch_user = sub { ... };  # 决不要这样做 —— 会在测试间泄漏

对于轻量级的 mock 对象,使用 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 测试模拟 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/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

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.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

相关技能