Communitygithub.com

perl-testing

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

perl-testing とは?

このスキルは、Perlアプリケーションのテスト戦略を包括的に提供します。Test2::V0やTest::Moreといった主要なテストフレームワークから、TDDのレッド・グリーン・リファクタリングサイクル、モッキング、Devel::Coverによるカバレッジ測定までをカバーします。テストの整理方法やproveコマンドの効率的な使い方、ベストプラクティスとアンチパターンも解説しており、初心者から熟練者まで幅広いPerl開発者が信頼性の高いテストスイートを構築できるよう支援します。また、Test2::V0の高度なアサーション(hash/arrayビルダー、例外テスト)や、Test::Moreからの移行方法も網羅しているため、現代的で堅牢なPerlテストを実現できます。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/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, 'ユーザーはBANされていない');

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

# パターンマッチング
like($error, qr/not found/i, 'エラーにnot foundとある');
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なのか?

  • hash/arrayビルダーによる優れた深い比較
  • 失敗時により良い診断出力
  • よりクリーンなスコープを持つサブテスト
  • 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();  # 存在しない — 余分なアイテムがないことを確認
    },
    '結果は期待されるリストと一致する'
);

# バッグ — 順序に依存しない比較
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する'
);

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

# 組み合わせパターン
subtest 'エラーハンドリング' => sub {
    ok(lives { parse_config('valid.json') }, '有効なconfigはパースできる');
    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' で読み込みます。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');

    # Good: モックは制御されたデータを返す
    $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', 'モックユーザーを返す');

    # 呼び出し回数の確認
    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;

ベストプラクティス

すべきこと

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

避けるべきこと

  • 実装をテストしない: 動作と出力をテストし、内部構造はテストしない
  • サブテスト間で状態を共有しない: 各サブテストは独立しているべき
  • 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, 'ラベル')
深い比較is($got, hash { field k => 'v'; etc() }, 'ラベル')
例外テストlike(dies { ... }, qr/msg/, 'ラベル')
例外なしテストok(lives { ... }, 'ラベル')
メソッドのモックTest::MockModule->new('Pkg')->mock(m => sub { ... })
テストのスキップSKIP: { skip '理由', $count unless $cond; ... }
TODOテストTODO: { local $TODO = '理由'; ... }

よくある落とし穴

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: @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/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.

関連スキル