Communitygithub.com

Perl 測試模式

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

Perl 測試模式 是什麼?

這項技能為 Perl 應用程式提供了全面的測試策略,支援 Test2::V0 和 Test::More 這兩種主要的測試框架。它遵循經典的紅-綠-重構(Red-Green-Refactor)TDD 工作流程,引導開發人員先編寫失敗的測試,再實作最簡程式碼使其通過,最後重構改善設計。技能內容涵蓋了從基本斷言(如 is、ok、is_deeply)到 Test2::V0 的進階功能,包括建構器(hash、array、bag)、子測試、例外測試和 SKIP/TODO 區塊。組織方面說明了 t/ 目錄結構和 prove 指令的各種用法(並行執行、狀態保存、CI 輸出),還包括 .proverc 配置。模擬部分介紹了 Test::MockModule 的安全 mock 模式,並強調避免猴子修補(monkey-patching)的最佳實踐。覆蓋率部分使用 Devel::Cover 產生 HTML 或文字報表,並支援設定最低覆蓋率閾值進行 CI 驗證。技能最後總結了常見陷阱(如忘記 done_testing、缺少 -l 旗標、過度模擬、測試污染)以及一份快速參考表,讓開發人員能高效地撰寫可靠且獨立的 Perl 測試。

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

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

Perl 測試模式 是做什麼的?

全面的 Perl 應用程式測試策略,使用 Test2::V0、Test::More、prove 和 TDD 方法論。

啟用時機

  • 編寫新的 Perl 程式碼(遵循 TDD:紅、綠、重構)
  • 為 Perl 模組或應用程式設計測試套件
  • 審查 Perl 測試覆蓋率
  • 設定 Perl 測試基礎設施
  • 從 Test::More 遷移至 Test2::V0
  • 除錯失敗的 Perl 測試

TDD 工作流程

始終遵循紅-綠-重構(RED-GREEN-REFACTOR)循環。

# 步驟 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 建構器的卓越深度比較
  • 失敗時更佳的診斷輸出
  • 子測試具有更清晰的範圍
  • 可透過 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();  # 不存在 — 驗證沒有多餘項目
    },
    '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

Fixtures 與 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');

    # Teardown 自動發生 (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

# 好作法:在 @INC 中包含 lib/
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.

相關技能