Source profileQuality 78/100

affaan-m/ECC/docs/zh-CN/skills/perl-testing/SKILL.md

perl-testing

Use it for testing and engineering tasks; the detail page covers purpose, installation, and practical steps.

Source repository stars
234,327
Declared platforms
0
Static risk flags
0
Last source update
2026-07-27
Source checked
2026-07-28

Decision brief

What it does—and where it fits

使用 Test2::V0、Test::More、prove 和 TDD 方法论为 Perl 应用程序提供全面的测试策略。

Best for

    Not for

    • Tasks that require unconfirmed production actions or broad system permissions.
    • Environments where the pinned source and install steps cannot be inspected.

    Compatibility matrix

    Platform support, with evidence labels

    PlatformStatusEvidenceWhat to check
    CodexNot declaredNo explicit evidencePortability before use
    Claude CodeNot declaredNo explicit evidencePortability before use
    CursorNot declaredNo explicit evidencePortability before use
    Gemini CLINot declaredNo explicit evidencePortability before use
    Open the compatibility checker

    Installation

    Inspect first. Install second.

    The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

    Source-detected install commandSource
    npx skills add https://github.com/affaan-m/ECC --skill "docs/zh-CN/skills/perl-testing"
    Safe inspection promptEditorial

    Inspect the Agent Skill "perl-testing" from https://github.com/affaan-m/ECC/blob/4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38/docs/zh-CN/skills/perl-testing/SKILL.md at commit 4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

    Workflow

    What the source asks the agent to do

    1. 01

      Step 1: RED — Write a failing test

      Review the “Step 1: RED — Write a failing test” section in the pinned source before continuing.

      Review and apply the “Step 1: RED — Write a failing test” source section.
    2. 02

      Step 2: GREEN — Write minimal implementation

      Review the “Step 2: GREEN — Write minimal implementation” section in the pinned source before continuing.

      Review and apply the “Step 2: GREEN — Write minimal implementation” source section.
    3. 03

      Step 3: REFACTOR — Improve while tests stay green

      Review the “Step 3: REFACTOR — Improve while tests stay green” section in the pinned source before continuing.

      Review and apply the “Step 3: REFACTOR — Improve while tests stay green” source section.
    4. 04

      Or step by step

      perl -MDevel::Cover -Ilib t/unit/user.t cover

      perl -MDevel::Cover -Ilib t/unit/user.t cover
    5. 05

      何时激活

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

      编写新的 Perl 代码(遵循 TDD:红、绿、重构)为 Perl 模块或应用程序设计测试套件审查 Perl 测试覆盖率

    Permission review

    Static risk signals and limitations

    No configured static risk pattern was detected

    This is not proof of safety. Runtime behavior, indirect dependencies, and hidden external systems are outside the static scan.

    Evidence record

    Why each signal appears

    EvidenceSourceComputedTestedEditorial
    SignalValueEvidence typeMeaning
    Quality score78/100ComputedDocumentation, specificity, maintenance, and trust rules
    Repository stars234,327SourceRepository attention, not individual Skill quality
    Compatibility0 platformsSourceDeclared in the catalog source record
    Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

    Pinned source

    Provenance and original SKILL.md

    Repository
    affaan-m/ECC
    Skill path
    docs/zh-CN/skills/perl-testing/SKILL.md
    Commit
    4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38
    License
    MIT
    Collected
    2026-07-28
    Default branch
    main
    View the original SKILL.md

    Perl 测试模式

    使用 Test2::V0、Test::More、prove 和 TDD 方法论为 Perl 应用程序提供全面的测试策略。

    何时激活

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

    TDD 工作流程

    始终遵循 RED-GREEN-REFACTOR 循环。

    # Step 1: RED — Write a failing test
    # 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;
    
    # Step 2: GREEN — Write minimal implementation
    # lib/Calculator.pm
    package Calculator;
    use v5.36;
    use Moo;
    
    sub add($self, $a, $b) {
        return $a + $b;
    }
    
    1;
    
    # Step 3: REFACTOR — Improve while tests stay green
    # Run: prove -lv t/unit/calculator.t
    

    Test::More 基础

    标准的 Perl 测试模块 —— 广泛使用,随核心发行。

    基本断言

    use v5.36;
    use Test::More;
    
    # Plan upfront or use done_testing
    # plan tests => 5;  # Fixed plan (optional)
    
    # Equality
    is($result, 42, 'returns correct value');
    isnt($result, 0, 'not zero');
    
    # Boolean
    ok($user->is_active, 'user is active');
    ok(!$user->is_banned, 'user is not banned');
    
    # Deep comparison
    is_deeply(
        $got,
        { name => 'Alice', roles => ['admin'] },
        'returns expected structure'
    );
    
    # Pattern matching
    like($error, qr/not found/i, 'error mentions not found');
    unlike($output, qr/password/, 'output hides password');
    
    # Type check
    isa_ok($obj, 'MyApp::User');
    can_ok($obj, 'save', 'delete');
    
    done_testing;
    

    SKIP 和 TODO

    use v5.36;
    use Test::More;
    
    # Skip tests conditionally
    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');
    }
    
    # Mark expected failures
    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;
    
    # Hash builder — check partial structure
    is(
        $user->to_hash,
        hash {
            field name  => 'Alice';
            field email => match(qr/\@example\.com$/);
            field age   => validator(sub { $_ >= 18 });
            # Ignore other fields
            etc();
        },
        'user has expected fields'
    );
    
    # Array builder
    is(
        $result,
        array {
            item 'first';
            item match(qr/^second/);
            item DNE();  # Does Not Exist — verify no extra items
        },
        'result matches expected list'
    );
    
    # Bag — order-independent comparison
    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;
    
    # Test that code dies
    like(
        dies { divide(10, 0) },
        qr/Division by zero/,
        'dies on division by zero'
    );
    
    # Test that code lives
    ok(lives { divide(10, 2) }, 'division succeeds') or note($@);
    
    # Combined pattern
    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 命令

    # Run all tests
    prove -l t/
    
    # Verbose output
    prove -lv t/
    
    # Run specific test
    prove -lv t/unit/user.t
    
    # Recursive search
    prove -lr t/
    
    # Parallel execution (8 jobs)
    prove -lr -j8 t/
    
    # Run only failing tests from last run
    prove -l --state=failed t/
    
    # Colored output with timer
    prove -l --color --timer t/
    
    # TAP output for CI
    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 {
        # Setup
        my $dir = tempdir(CLEANUP => 1);
        my $file = path($dir, 'input.txt');
        $file->spew_utf8("line1\nline2\nline3\n");
    
        # Test
        my $result = process_file("$file");
        is($result->{line_count}, 3, 'counts lines');
    
        # Teardown happens automatically (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');
    
        # Good: Mock returns controlled data
        $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');
    
        # Verify call count
        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 is automatically restored when $mock goes out of scope
    };
    
    # Bad: Monkey-patching without restoration
    # *MyApp::API::fetch_user = sub { ... };  # NEVER — leaks across tests
    

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

    使用 Devel::Cover 进行覆盖率分析

    运行覆盖率分析

    # Basic coverage report
    cover -test
    
    # Or step by step
    perl -MDevel::Cover -Ilib t/unit/user.t
    cover
    
    # HTML report
    cover -report html
    open cover_db/coverage.html
    
    # Specific thresholds
    cover -test -report text | grep 'Total'
    
    # CI-friendly: fail under threshold
    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

    # Bad: Test file runs but doesn't verify all tests executed
    use Test2::V0;
    is(1, 1, 'works');
    # Missing done_testing — silent bugs if test code is skipped
    
    # Good: Always end with done_testing
    use Test2::V0;
    is(1, 1, 'works');
    done_testing;
    

    缺少 -l 标志

    # Bad: Modules in lib/ not found
    prove t/unit/user.t
    # Can't locate MyApp/User.pm in @INC
    
    # Good: Include lib/ in @INC
    prove -l t/unit/user.t
    

    过度模拟

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

    测试污染

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

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

    Alternatives

    Compare before choosing