Communitygithub.com

Perl テストパターン

Test2::V0、Test::More、prove ランナー、モッキング、Devel::Cover によるカバレッジ、TDD 手法を用いた Perl テストパターン。Test2::V0 や Test::More で Perl テストを書くとき、またはカバレッジを計測するときに使用します。

Perl テストパターン とは?

このスキルは、Perl アプリケーション向けの包括的なテスト戦略を提供します。Test2::V0 モダンフレームワーク、Test::More ファンダメンタル、prove テストランナー、Devel::Cover によるカバレッジ、そして TDD (レッド・グリーン・リファクタリング) ワークフローを網羅しています。主な機能として、Test2::V0 のハッシュ/配列ビルダーを使った高度な深層比較、サブテストによる状態の分離、Test::MockModule を使った依存関係のモッキング、prove の並列実行、.proverc による設定管理が含まれます。Test::More から Test2::V0 への移行パターン、共通の落とし穴(done_testing の欠落、-l フラグの欠如、過剰なモッキング、テストの汚染)への対処法もカバーしています。prove -lr t/ で全テスト実行、cover -test でカバレッジレポート生成といった実用的なコマンドも提供します。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/tree/main/skills/perl-testing

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

Perl テストパターン は何をしますか?

Test2::V0、Test::More、prove、TDD 手法を用いた Perl アプリケーションの包括的なテスト戦略。

有効化するタイミング

  • 新しい Perl コードを書くとき(TDD に従う: レッド、グリーン、リファクタリング)
  • Perl モジュールやアプリケーションのテストスイートを設計するとき
  • Perl テストカバレッジをレビューするとき
  • Perl テストインフラをセットアップするとき
  • テストを Test::More から Test2::V0 に移行するとき
  • 失敗している Perl テストをデバッグするとき

TDD ワークフロー

常に RED-GREEN-REFACTOR サイクルに従います。

# ステップ 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, '2つの数字を加算');
    is($calc->add(-1, 1), 0, '負の数を処理');
};

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, '正しい値を返す');
isnt($result, 0, 'ゼロではない');

# 真偽値
ok($user->is_active, 'ユーザーはアクティブ');
ok(!$user->is_banned, 'ユーザーは禁止されていない');

# 深層比較
is_deeply(
    $got,
    { name => 'Alice', roles => ['admin'] },
    '期待される構造を返す'
);

# パターンマッチング
like($error, qr/not found/i, 'エラーに「見つかりません」と表示');
unlike($output, qr/password/, '出力にパスワードは表示されない');

# 型チェック
isa_ok($obj, 'MyApp::User');
can_ok($obj, 'save', 'delete');

done_testing;

SKIP と TODO

use v5.36;
use Test::More;

# 条件付きでテストをスキップ
SKIP: {
    skip 'データベースが設定されていません', 2 unless $ENV{TEST_DB};

    my $db = connect_db();
    ok($db->ping, 'データベースに到達可能');
    is($db->version, '15', '正しい PostgreSQL バージョン');
}

# 期待される失敗をマーク
TODO: {
    local $TODO = 'キャッシュはまだ実装されていません';
    is($cache->get('key'), '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();
    },
    'ユーザーは期待されるフィールドを持つ'
);

# 配列ビルダー
is(
    $result,
    array {
        item 'first';
        item match(qr/^second/);
        item DNE();  # 存在しない — 余分なアイテムがないことを確認
    },
    '結果は期待されるリストと一致する'
);

# Bag — 順序に依存しない比較
is(
    $tags,
    bag {
        item 'perl';
        item 'testing';
        item 'tdd';
    },
    '順序に関係なく必要なタグをすべて持つ'
);

サブテスト

use v5.36;
use Test2::V0;

subtest 'ユーザー作成' => sub {
    my $user = User->new(name => 'Alice', email => '[email protected]');
    ok($user, 'ユーザーオブジェクトが作成された');
    is($user->name, 'Alice', '名前が設定されている');
    is($user->email, '[email protected]', 'メールが設定されている');
};

subtest 'ユーザー検証' => sub {
    my $warnings = warns {
        User->new(name => '', email => 'bad');
    };
    ok($warnings, '無効なデータで警告が出る');
};

done_testing;

Test2 を使った例外テスト

use v5.36;
use Test2::V0;

# コードが die することをテスト
like(
    dies { divide(10, 0) },
    qr/Division by zero/,
    'ゼロ除算で die する'
);

# コードが live することをテスト
ok(lives { divide(10, 2) }, '除算が成功する') or note($@);

# 組み合わせパターン
subtest 'エラーハンドリング' => sub {
    ok(lives { parse_config('valid.json') }, '有効な設定はパースできる');
    like(
        dies { parse_config('missing.json') },
        qr/Cannot open/,
        '存在しないファイルはメッセージとともに die する'
    );
};

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 'ファイル処理' => 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, '行数をカウント');

    # ティアダウンは自動的に発生 (CLEANUP => 1)
};

共有テストヘルパー

再利用可能なヘルパーは t/lib/TestHelper.pm に配置し、use lib 't/lib' で読み込みます。create_test_db()、create_temp_dir()、fixture_path() のようなファクトリ関数を Exporter 経由でエクスポートします。

モッキング

Test::MockModule

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

subtest '外部APIをモック' => sub {
    my $mock = Test::MockModule->new('MyApp::API');

    # Good: モックは制御されたデータを返す
    $mock->mock(fetch_user => sub ($self, $id) {
        return { id => $id, name => 'モックユーザー', email => '[email protected]' };
    });

    my $api = MyApp::API->new;
    my $user = $api->fetch_user(42);
    is($user->{name}, 'モックユーザー', 'モックされたユーザーを返す');

    # 呼び出し回数を検証
    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 が2回呼ばれた');

    # $mock がスコープを外れると自動的にモックは復元される
};

# Bad: 復元されないモンキーパッチ
# *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 'データベース統合' => 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', 'ユーザーが挿入され、取得された');
};

done_testing;

ベストプラクティス

やるべきこと (DO)

  • TDD に従う: 実装の前にテストを書く (レッド-グリーン-リファクタリング)
  • Test2::V0 を使う: モダンなアサーション、より良い診断
  • サブテストを使う: 関連するアサーションをグループ化し、状態を分離
  • 外部依存関係をモックする: ネットワーク、データベース、ファイルシステム
  • prove -l を使う: 常に lib/ を @INC に含める
  • テストに明確な名前を付ける: '無効なパスワードでユーザーログインが失敗する'
  • エッジケースをテストする: 空文字列、undef、ゼロ、境界値
  • 80%以上のカバレッジを目指す: ビジネスロジックのパスに焦点を当てる
  • テストを高速に保つ: I/O をモックし、インメモリデータベースを使う

やってはいけないこと (DON'T)

  • 実装をテストしない: 動作と出力をテストし、内部はテストしない
  • サブテスト間で状態を共有しない: 各サブテストは独立しているべき
  • done_testing をスキップしない: 計画されたテストがすべて実行されたことを保証
  • 過剰にモックしない: 境界のみをモックし、テスト対象のコードはモックしない
  • 新しいプロジェクトに Test::More を使わない: Test2::V0 を優先
  • テストの失敗を無視しない: マージ前にすべてのテストが通らなければならない
  • CPAN モジュールをテストしない: ライブラリは正しく動作すると信頼する
  • 脆いテストを書かない: 過度に具体的な文字列マッチングを避ける

クイックリファレンス

タスクコマンド / パターン
全テスト実行prove -lr t/
1つのテストを冗長実行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: テストファイルは実行されるが、全テストが実行されたか確認しない
use Test2::V0;
is(1, 1, '動作する');
# done_testing がない — テストコードがスキップされると無言のバグ

# Good: 常に done_testing で終わる
use Test2::V0;
is(1, 1, '動作する');
done_testing;

-l フラグの欠如

# Bad: lib/ 内のモジュールが見つからない
prove t/unit/user.t
# Can't locate MyApp/User.pm in @INC

# Good: 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.

関連スキル