Communitygithub.com

perl-testing

Modèles de tests Perl utilisant Test2::V0, Test::More, le lanceur prove, le mocking, la couverture avec Devel::Cover et la méthodologie TDD. À utiliser lors de l'écriture de tests Perl avec Test2::V0 ou Test::More, ou pour mesurer la couverture.

Qu'est-ce que perl-testing ?

Ce skill fournit un guide complet pour écrire des tests Perl robustes en utilisant les frameworks modernes Test2::V0 et Test::More, le lanceur de tests prove, le mocking avec Test::MockModule et Test::MockObject, et la mesure de couverture avec Devel::Cover. Il est particulièrement utile pour les développeurs qui adoptent une approche TDD (développement piloté par les tests) en suivant le cycle rouge-vert-réfactorisation. Le skill couvre l'organisation des tests, les assertions de base et avancées, les sous-tests, la gestion des fixtures, et les règles de bonne pratique à suivre (comme éviter le partage d'état entre sous-tests, ne pas omettre 'done_testing', et privilégier Test2::V0 pour les nouveaux projets) ainsi que les pièges courants à éviter. Il fonctionne sur toute plateforme exécutant Perl 5 avec les modules CPAN standards, et s'intègre aux environnements CI via les rapports TAP.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/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 Tests Perl

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

Quand Activer

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

Workflow TDD

Toujours suivre le cycle ROUGE-VERT-REFACTORISER.

# É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 nombres 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 : REFACTORISER — Améliorer tout en gardant les tests verts
# Exécuter : prove -lv t/unit/calculator.t

Fondamentaux de Test::More

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

Assertions de Base

use v5.36;
use Test::More;

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

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

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

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

# Correspondance de motif
like($erreur, qr/introuvable/i, 'l\'erreur mentionne introuvable');
unlike($sortie, qr/mot_de_passe/, 'la sortie cache le mot de passe');

# Vérification de type
isa_ok($obj, 'MonApp::Utilisateur');
can_ok($obj, 'sauvegarder', 'supprimer');

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 à moins que $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 de hachages/tableaux
  • Meilleure sortie diagnostique 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 de hachage — vérifier une structure partielle
is(
    $utilisateur->to_hash,
    hash {
        field nom  => 'Alice';
        field email => match(qr/\@exemple\.com$/);
        field age   => validator(sub { $_ >= 18 });
        # Ignorer les autres champs
        etc();
    },
    'l\'utilisateur a les champs attendus'
);

# Constructeur de tableau
is(
    $resultat,
    array {
        item 'premier';
        item match(qr/^second/);
        item DNE();  # N'existe pas — vérifier aucun élément supplémentaire
    },
    'le résultat correspond à la liste attendue'
);

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

Sous-tests

use v5.36;
use Test2::V0;

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

subtest 'Validation d\'utilisateur' => sub {
    my $avertissements = warns {
        User->new(nom => '', email => 'mauvais');
    };
    ok($avertissements, 'avertit sur des données invalides');
};

done_testing;

Test d'Exceptions avec Test2

use v5.36;
use Test2::V0;

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

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

# Motif combiné
subtest 'gestion des erreurs' => sub {
    ok(lives { parse_config('valide.json') }, 'la configuration valide est analysé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 de Répertoire

t/
├── 00-chargement.t        # Vérifier que les modules se compilent
├── 01-base.t              # Fonctionnalités de base
├── unit/
│   ├── config.t           # Tests unitaires par module
│   ├── utilisateur.t
│   └── util.t
├── integration/
│   ├── base_de_donnees.t
│   └── api.t
├── lib/
│   └── AideTest.pm       # Utilitaires de test partagés
└── fixtures/
    ├── config.json        # Fichiers de données de test
    └── utilisateurs.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/utilisateur.t

# Recherche récursive
prove -lr t/

# Exécution parallèle (8 tâches)
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 minuteur
prove -l --color --timer t/

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

Configuration .proverc

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

Fixtures et Configuration/Nettoyage

Isolation des Sous-tests

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

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

    # Test
    my $resultat = traiter_fichier("$fichier");
    is($resultat->{nombre_lignes}, 3, 'compte les lignes');

    # Le nettoyage se fait automatiquement (CLEANUP => 1)
};

Aides de Test Partagés

Placez les aides réutilisables dans t/lib/AideTest.pm et chargez-les avec use lib 't/lib'. Exportez des fonctions de fabrique comme creer_bd_test(), creer_repertoire_temp(), et chemin_fixture() via Exporter.

Mocking

Test::MockModule

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

subtest 'moquer une API externe' => sub {
    my $mock = Test::MockModule->new('MonApp::API');

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

    my $api = MonApp::API->new;
    my $utilisateur = $api->recuperer_utilisateur(42);
    is($utilisateur->{nom}, 'Utilisateur Mock', 'retourne l\'utilisateur mocké');

    # Vérifier le nombre d'appels
    my $nombre_appels = 0;
    $mock->mock(recuperer_utilisateur => sub { $nombre_appels++; return {} });
    $api->recuperer_utilisateur(1);
    $api->recuperer_utilisateur(2);
    is($nombre_appels, 2, 'recuperer_utilisateur appelé deux fois');

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

# Mauvais : Monkey-patching sans restauration
# *MonApp::API::recuperer_utilisateur = sub { ... };  # JAMAIS — fuit entre 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écuter la Couverture

# Rapport de couverture de base
cover -test

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

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

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

# Compatible CI : échouer en dessous d'un 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, et moquez 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 $ligne = $dbh->selectrow_hashref('SELECT * FROM utilisateurs WHERE nom = ?', undef, 'Alice');
    is($ligne->{nom}, 'Alice', 'utilisateur inséré et récupéré');
};

done_testing;

Bonnes Pratiques

À FAIRE

  • Suivre le TDD : Écrire les tests avant l'implémentation (rouge-vert-refactoriser)
  • Utiliser Test2::V0 : Assertions modernes, meilleurs diagnostics
  • Utiliser les sous-tests : Grouper les assertions liées, isoler l'état
  • Moquer 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 : 'connexion utilisateur avec mot de passe invalide échoue'
  • Tester les cas limites : Chaînes vides, undef, zéro, valeurs aux limites
  • Viser 80 %+ de couverture : Se concentrer sur les chemins métier
  • Garder les tests rapides : Moquer 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 le résultat, pas les détails internes
  • Ne pas partager l'état entre les sous-tests : Chaque sous-test doit être indépendant
  • Ne pas omettre done_testing : Garantit que tous les tests planifiés ont été exécutés
  • Ne pas trop moquer : Moquer 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
  • Ne pas écrire de tests fragiles : Éviter la correspondance de chaîne trop spécifique

Référence Rapide

TâcheCommande / Modèle
Exécuter tous les testsprove -lr t/
Exécuter un test en verbeuxprove -lv t/unit/utilisateur.t
Exécution parallèleprove -lr -j8 t/
Rapport de couverturecover -test && cover -report html
Tester l'égalitéis($obtenu, $attendu, 'label')
Comparaison profondeis($obtenu, hash { field k => 'v'; etc() }, 'label')
Tester une exceptionlike(dies { ... }, qr/msg/, 'label')
Tester aucune exceptionok(lives { ... }, 'label')
Moquer une méthodeTest::MockModule->new('Pkg')->mock(m => sub { ... })
Sauter des testsSKIP: { skip 'raison', $compte à moins que $condition; ... }
Tests TODOTODO: { local $TODO = 'raison'; ... }

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é

# Bien : 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/utilisateur.t
# Impossible de localiser MonApp/Utilisateur.pm dans @INC

# Bien : Inclure lib/ dans @INC
prove -l t/unit/utilisateur.t

Sur-Moquage

Moquez 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 de Test

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

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

Skills associés