Communitygithub.com

perl-testing

Perl 測試模式,使用 Test2::V0、Test::More、prove 執行器、模擬技術、Devel::Cover 覆蓋率以及 TDD 方法論。在編寫 Perl 測試(Test2::V0 或 Test::More)或測量覆蓋率時使用。

perl-testing 是什麼?

這份技能提供了一個完整的 Perl 測試策略指南,專注於現代測試框架 Test2::V0 以及經典的 Test::More,並搭配 TDD(紅-綠-重構)工作流程。它詳細說明了何時啟用此技能,例如編寫新程式碼、設計測試套件、審查測試覆蓋率、設定測試基礎架構、從 Test::More 遷移至 Test2::V0,以及除錯失敗的測試。 技能的核心是提倡遵循 RED-GREEN-REFACTOR 週期,並提供了大量的程式碼範例來說明如何撰寫單元測試、整合測試,以及使用 build (hash、array、bag) 來進行深度比較。此外,它還包含了使用 prove 執行測試的進階命令列選項,以及如何透過 .proverc 進行客製化配置。 為了確保測試的可靠性和隔離性,技能深入講解了使用 Test::MockModule 和 Test::MockObject 進行模擬測試的技巧,以及使用 File::Temp 和 subtest 來管理測試裝置與設定/清除。最後,它還介紹了如何使用 Devel::Cover 來產生覆蓋率報告(包含 HTML 格式),並在 CI 流程中設定低於門檻值時失敗。整體而言,這份技能支援 Claude Code、Cursor、Codex 等 AI 代理開發平台,幫助開發者建立高品質、可維護的 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?

  • 使用 hash/array builder 進行更優越的深度比較
  • 失敗時提供更好的診斷輸出
  • 具有更清晰作用域的次測試
  • 可透過 Test2::Tools::* 插件擴展
  • 向下相容 Test::More 測試

使用 Builder 進行深度比較

use v5.36;
use Test2::V0;

# Hash builder — 檢查部分結構
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 builder
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 測試模擬 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.

相關技能