Communitygithub.com

perl-testing

Modèles de test Perl utilisant Test2::V0, Test::More, le lanceur prove, le mock, la couverture avec Devel::Cover et la méthodologie TDD.

Qu'est-ce que perl-testing ?

Cette compétence fournit une stratégie complète de test pour les applications Perl, en mettant l'accent sur les frameworks modernes (Test2::V0) et classiques (Test::More). Elle couvre l'ensemble du cycle de développement piloté par les tests (TDD : rouge-vert-refactor) avec des exemples concrets en Perl utilisant Moo. Elle décrit l'organisation des tests (hiérarchie de répertoires), l'utilisation du lanceur prove (avec options parallèles, colorisation et état), la mise en œuvre de mocks via Test::MockModule, et la mesure de couverture avec Devel::Cover (génération de rapports HTML/texte et seuils CI). La compétence détaille les bonnes pratiques (tester les cas limites, éviter de sursimuler, utiliser subtests et done_testing) et les pièges courants (oublier done_testing, omettre le flag -l de prove, pollution d'état entre tests). Elle s'adresse aux développeurs Perl qui souhaitent écrire des tests fiables, maintenables et rapides, que ce soit pour de nouveaux projets (Test2::V0 recommandé) ou pour migrer des suites existantes depuis Test::More.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/perl-testing

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Modèles de test Perl

Stratégies complètes de test pour les applications Perl utilisant Test2::V0, Test::More, prove et la méthodologie TDD.

Quand activer

  • Écrire du nouveau code Perl (suivre TDD : rouge, vert, refactor)
  • Concevoir des suites de test pour des modules ou applications Perl
  • Réviser la couverture de test Perl
  • Mettre en place l'infrastructure de test Perl
  • Migrer des tests de Test::More vers Test2::V0
  • Déboguer des tests Perl qui échouent

Workflow TDD

Suivez toujours le cycle ROUGE-VERT-REFACTOR.

# Étape 1 : ROUGE — Écrire un test qui échoue
# 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, 'additionne deux nombres');
    is($calc->add(-1, 1), 0, 'gère les négatifs');
};

done_testing;

# Étape 2 : VERT — Écrire l'implémentation minimale
# lib/Calculator.pm
package Calculator;
use v5.36;
use Moo;

sub add($self, $a, $b) {
    return $a + $b;
}

1;

# Étape 3 : REFACTOR — Améliorer tout en gardant les tests au vert
# Exécuter : prove -lv t/unit/calculator.t

Fondamentaux de Test::More

Le module de test Perl standard — largement utilisé, inclus dans le cœur.

Assertions de base

use v5.36;
use Test::More;

# Planifier à l'avance ou utiliser done_testing
# plan tests => 5;  # Plan fixe (optionnel)

# Égalité
is($result, 42, 'retourne la bonne valeur');
isnt($result, 0, 'pas zéro');

# Booléen
ok($user->is_active, 'l\'utilisateur est actif');
ok(!$user->is_banned, 'l\'utilisateur n\'est pas banni');

# Comparaison profonde
is_deeply(
    $got,
    { name => 'Alice', roles => ['admin'] },
    'retourne la structure attendue'
);

# Correspondance de motif
like($error, qr/not found/i, 'l\'erreur mentionne "not found"');
unlike($output, qr/password/, 'la sortie cache le mot de passe');

# Vérification de type
isa_ok($obj, 'MyApp::User');
can_ok($obj, 'save', 'delete');

done_testing;

SKIP et TODO

use v5.36;
use Test::More;

# Sauter des tests conditionnellement
SKIP: {
    skip 'Aucune base de données configurée', 2 unless $ENV{TEST_DB};

    my $db = connect_db();
    ok($db->ping, 'la base de données est accessible');
    is($db->version, '15', 'version PostgreSQL correcte');
}

# Marquer des échecs attendus
TODO: {
    local $TODO = 'Cache non encore implémenté';
    is($cache->get('clé'), 'valeur', 'le cache retourne la valeur');
}

done_testing;

Framework moderne Test2::V0

Test2::V0 est le remplacement moderne de Test::More — assertions plus riches, meilleurs diagnostics et extensible.

Pourquoi Test2 ?

  • Comparaison profonde supérieure avec les constructeurs hash/array
  • Meilleure sortie de diagnostic en cas d'échec
  • Sous-tests avec une portée plus propre
  • Extensible via les plugins Test2::Tools::*
  • Rétrocompatible avec les tests Test::More

Comparaison profonde avec les constructeurs

use v5.36;
use Test2::V0;

# Constructeur hash — vérifier une structure partielle
is(
    $user->to_hash,
    hash {
        field name  => 'Alice';
        field email => match(qr/\@example\.com$/);
        field age   => validator(sub { $_ >= 18 });
        # Ignorer les autres champs
        etc();
    },
    'l\'utilisateur a les champs attendus'
);

# Constructeur array
is(
    $result,
    array {
        item 'premier';
        item match(qr/^second/);
        item DNE();  # N'existe pas — vérifier qu'il n'y a pas d'éléments supplémentaires
    },
    'le résultat correspond à la liste attendue'
);

# Bag — comparaison indépendante de l'ordre
is(
    $tags,
    bag {
        item 'perl';
        item 'testing';
        item 'tdd';
    },
    'a toutes les balises requises, quel que soit l\'ordre'
);

Sous-tests

use v5.36;
use Test2::V0;

subtest 'Création d\'utilisateur' => sub {
    my $user = User->new(name => 'Alice', email => '[email protected]');
    ok($user, 'objet utilisateur créé');
    is($user->name, 'Alice', 'le nom est défini');
    is($user->email, '[email protected]', 'l\'email est défini');
};

subtest 'Validation d\'utilisateur' => sub {
    my $warnings = warns {
        User->new(name => '', email => 'mauvais');
    };
    ok($warnings, 'avertit en cas de données invalides');
};

done_testing;

Test d'exceptions avec Test2

use v5.36;
use Test2::V0;

# Tester que le code meurt
like(
    dies { divide(10, 0) },
    qr/Division par zéro/,
    'meurt sur division par zéro'
);

# Tester que le code vit
ok(lives { divide(10, 2) }, 'la division réussit') or note($@);

# Motif combiné
subtest 'gestion des erreurs' => sub {
    ok(lives { parse_config('valide.json') }, 'la config valide est parsée');
    like(
        dies { parse_config('manquant.json') },
        qr/Impossible d\'ouvrir/,
        'le fichier manquant meurt avec un message'
    );
};

done_testing;

Organisation des tests et prove

Structure des répertoires

t/
├── 00-load.t              # Vérifier que les modules compilent
├── 01-basic.t             # Fonctionnalité de base
├── unit/
│   ├── config.t           # Tests unitaires par module
│   ├── user.t
│   └── util.t
├── integration/
│   ├── database.t
│   └── api.t
├── lib/
│   └── TestHelper.pm      # Utilitaires de test partagés
└── fixtures/
    ├── config.json        # Fichiers de données de test
    └── users.csv

Commandes prove

# Exécuter tous les tests
prove -l t/

# Sortie verbeuse
prove -lv t/

# Exécuter un test spécifique
prove -lv t/unit/user.t

# Recherche récursive
prove -lr t/

# Exécution parallèle (8 jobs)
prove -lr -j8 t/

# Exécuter uniquement les tests ayant échoué lors de la dernière exécution
prove -l --state=failed t/

# Sortie colorée avec timer
prove -l --color --timer t/

# Sortie TAP pour CI
prove -l --formatter TAP::Formatter::JUnit t/ > results.xml

Configuration .proverc

-l
--color
--timer
-r
-j4
--state=save

Fixtures et Setup/Teardown

Isolation des sous-tests

use v5.36;
use Test2::V0;
use File::Temp qw(tempdir);
use Path::Tiny;

subtest 'traitement de fichier' => sub {
    # Setup
    my $dir = tempdir(CLEANUP => 1);
    my $file = path($dir, 'entree.txt');
    $file->spew_utf8("ligne1\nligne2\nligne3\n");

    # Test
    my $result = process_file("$file");
    is($result->{line_count}, 3, 'compte les lignes');

    # Teardown se produit automatiquement (CLEANUP => 1)
};

Helpers de test partagés

Placez les helpers réutilisables dans t/lib/TestHelper.pm et chargez-les avec use lib 't/lib'. Exportez des fonctions factory comme create_test_db(), create_temp_dir() et fixture_path() via Exporter.

Mocking

Test::MockModule

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

subtest 'mocker l\'API externe' => sub {
    my $mock = Test::MockModule->new('MyApp::API');

    # Bien : Mock retourne des données contrôlées
    $mock->mock(fetch_user => sub ($self, $id) {
        return { id => $id, name => 'Utilisateur Mock', email => '[email protected]' };
    });

    my $api = MyApp::API->new;
    my $user = $api->fetch_user(42);
    is($user->{name}, 'Utilisateur Mock', 'retourne l\'utilisateur mocké');

    # Vérifier le nombre d'appels
    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 appelé deux fois');

    # Le mock est automatiquement restauré quand $mock sort de portée
};

# Mauvais : Monkey-patching sans restauration
# *MyApp::API::fetch_user = sub { ... };  # JAMAIS — pollue les tests

Pour des objets mock légers, utilisez Test::MockObject pour créer des doubles de test injectables avec ->mock() et vérifier les appels avec ->called_ok().

Couverture avec Devel::Cover

Exécution de la couverture

# Rapport de couverture de base
cover -test

# Ou étape par étape
perl -MDevel::Cover -Ilib t/unit/user.t
cover

# Rapport HTML
cover -report html
open cover_db/coverage.html

# Seuils spécifiques
cover -test -report text | grep 'Total'

# Adapté au CI : échouer sous le seuil
cover -test && cover -report text -select '^lib/' \
  | perl -ne 'if (/Total.*?(\d+\.\d+)/) { exit 1 if $1 < 80 }'

Tests d'intégration

Utilisez SQLite en mémoire pour les tests de base de données, mockez HTTP::Tiny pour les tests d'API.

use v5.36;
use Test2::V0;
use DBI;

subtest 'intégration base de données' => sub {
    my $dbh = DBI->connect('dbi:SQLite:dbname=:memory:', '', '', {
        RaiseError => 1,
    });
    $dbh->do('CREATE TABLE utilisateurs (id INTEGER PRIMARY KEY, nom TEXT)');

    $dbh->prepare('INSERT INTO utilisateurs (nom) VALUES (?)')->execute('Alice');
    my $row = $dbh->selectrow_hashref('SELECT * FROM utilisateurs WHERE nom = ?', undef, 'Alice');
    is($row->{nom}, 'Alice', 'utilisateur inséré et récupéré');
};

done_testing;

Bonnes pratiques

À FAIRE

  • Suivre TDD : Écrire les tests avant l'implémentation (rouge-vert-refactor)
  • Utiliser Test2::V0 : Assertions modernes, meilleurs diagnostics
  • Utiliser les sous-tests : Grouper les assertions liées, isoler l'état
  • Mocker les dépendances externes : Réseau, base de données, système de fichiers
  • Utiliser prove -l : Toujours inclure lib/ dans @INC
  • Nommer les tests clairement : 'la connexion utilisateur avec mot de passe invalide échoue'
  • Tester les cas limites : Chaînes vides, undef, zéro, valeurs aux limites
  • Viser une couverture de 80%+ : Se concentrer sur les chemins métier
  • Garder les tests rapides : Mocker les E/S, utiliser des bases de données en mémoire

À NE PAS FAIRE

  • Ne pas tester l'implémentation : Tester le comportement et la sortie, pas les détails internes
  • Ne pas partager l'état entre sous-tests : Chaque sous-test doit être indépendant
  • Ne pas sauter done_testing : Garantit que tous les tests planifiés ont été exécutés
  • Ne pas surmocker : Mocker uniquement les frontières, pas le code testé
  • Ne pas utiliser Test::More pour les nouveaux projets : Préférer Test2::V0
  • Ne pas ignorer les échecs de test : Tous les tests doivent passer avant la fusion
  • Ne pas tester les modules CPAN : Faire confiance aux bibliothèques pour fonctionner correctement
  • Ne pas écrire de tests fragiles : Éviter les correspondances de chaînes trop spécifiques

Référence rapide

TâcheCommande / Modèle
Exécuter tous les testsprove -lr t/
Exécuter un test en verbeuxprove -lv t/unit/user.t
Exécution parallèleprove -lr -j8 t/
Rapport de couverturecover -test && cover -report html
Tester l'égalitéis($got, $expected, 'label')
Comparaison profondeis($got, hash { field k => 'v'; etc() }, 'label')
Tester une exceptionlike(dies { ... }, qr/msg/, 'label')
Tester pas d'exceptionok(lives { ... }, 'label')
Mocker une méthodeTest::MockModule->new('Pkg')->mock(m => sub { ... })
Sauter des testsSKIP: { skip 'reason', $count unless $cond; ... }
Tests TODOTODO: { local $TODO = 'reason'; ... }

Pièges courants

Oublier done_testing

# Mauvais : Le fichier de test s'exécute mais ne vérifie pas que tous les tests ont été exécutés
use Test2::V0;
is(1, 1, 'fonctionne');
# done_testing manquant — bugs silencieux si le code de test est sauté

# Bon : Toujours terminer par done_testing
use Test2::V0;
is(1, 1, 'fonctionne');
done_testing;

Drapeau -l manquant

# Mauvais : Modules dans lib/ non trouvés
prove t/unit/user.t
# Impossible de localiser MyApp/User.pm dans @INC

# Bon : Inclure lib/ dans @INC
prove -l t/unit/user.t

Surmocking

Mockez la dépendance, pas le code testé. Si votre test vérifie seulement qu'un mock retourne ce que vous lui avez dit, il ne teste rien.

Pollution des tests

Utilisez des variables my à l'intérieur des sous-tests — jamais our — pour éviter que l'état ne fuite entre les tests.

Souvenez-vous : Les tests sont votre filet de sécurité. Gardez-les rapides, ciblés et indépendants. Utilisez Test2::V0 pour les nouveaux projets, prove pour l'exécution et Devel::Cover pour la responsabilité.

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.

Skills associés