Communitygithub.com

perl-testing

Patrones de prueba en Perl usando Test2::V0, Test::More, prove runner, mocking, cobertura con Devel::Cover y metodología TDD. Actívalo al escribir pruebas Perl con Test2::V0 o Test::More, o al medir cobertura.

¿Qué es perl-testing?

Este skill proporciona una guía completa para escribir y ejecutar pruebas en Perl, cubriendo desde los fundamentos con Test::More hasta el framework moderno Test2::V0. Incluye patrones de diseño de pruebas, ejemplos de mocking, organización de suites con prove, y medición de cobertura con Devel::Cover. Sigue estrictamente el ciclo TDD (rojo-verde-refactor) y ofrece prácticas recomendadas para evitar errores comunes como olvidar `done_testing`, no incluir `-l` en prove, o sobremoquear dependencias. Está orientado a desarrolladores que trabajan con aplicaciones Perl modernas y desean adoptar un enfoque riguroso y eficiente en las pruebas.

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

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Patrones de Prueba en Perl

Estrategias integrales de prueba para aplicaciones Perl usando Test2::V0, Test::More, prove y metodología TDD.

Cuándo activarlo

  • Al escribir nuevo código Perl (seguir TDD: rojo, verde, refactorizar)
  • Al diseñar suites de prueba para módulos o aplicaciones Perl
  • Al revisar la cobertura de pruebas Perl
  • Al configurar la infraestructura de pruebas Perl
  • Al migrar pruebas de Test::More a Test2::V0
  • Al depurar pruebas Perl que fallan

Flujo de trabajo TDD

Sigue siempre el ciclo ROJO-VERDE-REFACTORIZAR.

# Paso 1: ROJO — Escribe una prueba que falle
# t/unit/calculator.t
use v5.36;
use Test2::V0;

use lib 'lib';
use Calculator;

subtest 'suma' => sub {
    my $calc = Calculator->new;
    is($calc->add(2, 3), 5, 'suma dos números');
    is($calc->add(-1, 1), 0, 'maneja negativos');
};

done_testing;

# Paso 2: VERDE — Escribe la implementación mínima
# lib/Calculator.pm
package Calculator;
use v5.36;
use Moo;

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

1;

# Paso 3: REFACTORIZAR — Mejora mientras las pruebas sigan en verde
# Ejecuta: prove -lv t/unit/calculator.t

Fundamentos de Test::More

El módulo de prueba estándar de Perl — ampliamente usado, incluido en el núcleo.

Afirmaciones Básicas

use v5.36;
use Test::More;

# Planificar de antemano o usar done_testing
# plan tests => 5;  # Plan fijo (opcional)

# Igualdad
is($result, 42, 'devuelve el valor correcto');
isnt($result, 0, 'no es cero');

# Booleano
ok($user->is_active, 'el usuario está activo');
ok(!$user->is_banned, 'el usuario no está baneado');

# Comparación profunda
is_deeply(
    $got,
    { name => 'Alice', roles => ['admin'] },
    'devuelve la estructura esperada'
);

# Coincidencia de patrones
like($error, qr/not found/i, 'el error menciona "not found"');
unlike($output, qr/password/, 'la salida oculta la contraseña');

# Verificación de tipo
isa_ok($obj, 'MyApp::User');
can_ok($obj, 'save', 'delete');

done_testing;

SKIP y TODO

use v5.36;
use Test::More;

# Saltar pruebas condicionalmente
SKIP: {
    skip 'No hay base de datos configurada', 2 unless $ENV{TEST_DB};

    my $db = connect_db();
    ok($db->ping, 'la base de datos es accesible');
    is($db->version, '15', 'versión correcta de PostgreSQL');
}

# Marcar fallos esperados
TODO: {
    local $TODO = 'Caché aún no implementada';
    is($cache->get('key'), 'value', 'la caché devuelve el valor');
}

done_testing;

Marco Moderno Test2::V0

Test2::V0 es el reemplazo moderno de Test::More — afirmaciones más ricas, mejor diagnóstico y extensible.

¿Por qué Test2?

  • Comparación profunda superior con constructores de hash/array
  • Mejor salida de diagnóstico en fallos
  • Subpruebas con ámbito más limpio
  • Extensible mediante plugins Test2::Tools::*
  • Compatible hacia atrás con pruebas de Test::More

Comparación Profunda con Constructores

use v5.36;
use Test2::V0;

# Constructor de hash — verificar estructura parcial
is(
    $user->to_hash,
    hash {
        field name  => 'Alice';
        field email => match(qr/\@example\.com$/);
        field age   => validator(sub { $_ >= 18 });
        # Ignorar otros campos
        etc();
    },
    'el usuario tiene los campos esperados'
);

# Constructor de array
is(
    $result,
    array {
        item 'first';
        item match(qr/^second/);
        item DNE();  # Does Not Exist — verificar que no hay elementos extra
    },
    'el resultado coincide con la lista esperada'
);

# Bag — comparación independiente del orden
is(
    $tags,
    bag {
        item 'perl';
        item 'testing';
        item 'tdd';
    },
    'tiene todos los tags requeridos sin importar el orden'
);

Subpruebas

use v5.36;
use Test2::V0;

subtest 'Creación de usuario' => sub {
    my $user = User->new(name => 'Alice', email => '[email protected]');
    ok($user, 'objeto usuario creado');
    is($user->name, 'Alice', 'el nombre está establecido');
    is($user->email, '[email protected]', 'el email está establecido');
};

subtest 'Validación de usuario' => sub {
    my $warnings = warns {
        User->new(name => '', email => 'bad');
    };
    ok($warnings, 'advierte sobre datos inválidos');
};

done_testing;

Pruebas de Excepciones con Test2

use v5.36;
use Test2::V0;

# Probar que el código lanza una excepción
like(
    dies { divide(10, 0) },
    qr/División por cero/,
    'lanza excepción al dividir por cero'
);

# Probar que el código no lanza excepción
ok(lives { divide(10, 2) }, 'la división tiene éxito') or note($@);

# Patrón combinado
subtest 'manejo de errores' => sub {
    ok(lives { parse_config('valid.json') }, 'configuración válida se parsea');
    like(
        dies { parse_config('missing.json') },
        qr/No se puede abrir/,
        'archivo faltante lanza excepción con mensaje'
    );
};

done_testing;

Organización de Pruebas y prove

Estructura de Directorios

t/
├── 00-load.t              # Verificar que los módulos compilan
├── 01-basic.t             # Funcionalidad central
├── unit/
│   ├── config.t           # Pruebas unitarias por módulo
│   ├── user.t
│   └── util.t
├── integration/
│   ├── database.t
│   └── api.t
├── lib/
│   └── TestHelper.pm      # Utilidades de prueba compartidas
└── fixtures/
    ├── config.json        # Archivos de datos de prueba
    └── users.csv

Comandos de prove

# Ejecutar todas las pruebas
prove -l t/

# Salida verbosa
prove -lv t/

# Ejecutar una prueba específica
prove -lv t/unit/user.t

# Búsqueda recursiva
prove -lr t/

# Ejecución paralela (8 trabajos)
prove -lr -j8 t/

# Ejecutar solo pruebas que fallaron en la última ejecución
prove -l --state=failed t/

# Salida coloreada con temporizador
prove -l --color --timer t/

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

Configuración .proverc

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

Fixtures y Configuración/Limpieza

Aislamiento de Subpruebas

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

subtest 'procesamiento de archivos' => sub {
    # Configuración
    my $dir = tempdir(CLEANUP => 1);
    my $file = path($dir, 'input.txt');
    $file->spew_utf8("linea1\nlinea2\nlinea3\n");

    # Prueba
    my $result = process_file("$file");
    is($result->{line_count}, 3, 'cuenta líneas');

    # La limpieza ocurre automáticamente (CLEANUP => 1)
};

Ayudantes de Prueba Compartidos

Coloca ayudantes reutilizables en t/lib/TestHelper.pm y cárgalos con use lib 't/lib'. Exporta funciones de fábrica como create_test_db(), create_temp_dir() y fixture_path() mediante Exporter.

Mocking

Test::MockModule

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

subtest 'simular API externa' => sub {
    my $mock = Test::MockModule->new('MyApp::API');

    # Bien: Mock devuelve datos controlados
    $mock->mock(fetch_user => sub ($self, $id) {
        return { id => $id, name => 'Usuario Simulado', email => '[email protected]' };
    });

    my $api = MyApp::API->new;
    my $user = $api->fetch_user(42);
    is($user->{name}, 'Usuario Simulado', 'devuelve usuario simulado');

    # Verificar número de llamadas
    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 fue llamado dos veces');

    # El mock se restaura automáticamente cuando $mock sale del ámbito
};

# Mal: Monkey-patching sin restauración
# *MyApp::API::fetch_user = sub { ... };  # NUNCA — se filtra entre pruebas

Para objetos mock ligeros, usa Test::MockObject para crear dobles de prueba inyectables con ->mock() y verifica llamadas con ->called_ok().

Cobertura con Devel::Cover

Ejecutar Cobertura

# Informe básico de cobertura
cover -test

# O paso a paso
perl -MDevel::Cover -Ilib t/unit/user.t
cover

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

# Umbrales específicos
cover -test -report text | grep 'Total'

# Amigable para CI: fallar por debajo del umbral
cover -test && cover -report text -select '^lib/' \
  | perl -ne 'if (/Total.*?(\d+\.\d+)/) { exit 1 if $1 < 80 }'

Pruebas de Integración

Usa SQLite en memoria para pruebas de base de datos, simula HTTP::Tiny para pruebas de API.

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

subtest 'integración con base de datos' => 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', 'usuario insertado y recuperado');
};

done_testing;

Buenas Prácticas

HACER

  • Seguir TDD: Escribir pruebas antes de la implementación (rojo-verde-refactorizar)
  • Usar Test2::V0: Afirmaciones modernas, mejor diagnóstico
  • Usar subpruebas: Agrupar afirmaciones relacionadas, aislar estado
  • Simular dependencias externas: Red, base de datos, sistema de archivos
  • Usar prove -l: Incluir siempre lib/ en @INC
  • Nombrar pruebas claramente: 'el inicio de sesión con contraseña inválida falla'
  • Probar casos límite: Cadenas vacías, undef, cero, valores frontera
  • Apuntar a 80%+ de cobertura: Enfocarse en rutas de lógica de negocio
  • Mantener pruebas rápidas: Simular E/S, usar bases de datos en memoria

NO HACER

  • No probar la implementación: Probar comportamiento y resultado, no internas
  • No compartir estado entre subpruebas: Cada subprueba debe ser independiente
  • No omitir done_testing: Asegura que todas las pruebas planificadas se ejecutaron
  • No sobremoquear: Simular solo los límites, no el código bajo prueba
  • No usar Test::More para proyectos nuevos: Preferir Test2::V0
  • No ignorar fallos en pruebas: Todas las pruebas deben pasar antes de fusionar
  • No probar módulos de CPAN: Confiar en que las bibliotecas funcionan correctamente
  • No escribir pruebas frágiles: Evitar coincidencias de cadenas demasiado específicas

Referencia Rápida

TareaComando / Patrón
Ejecutar todas las pruebasprove -lr t/
Ejecutar una prueba con verbosidadprove -lv t/unit/user.t
Ejecución paralela de pruebasprove -lr -j8 t/
Informe de coberturacover -test && cover -report html
Probar igualdadis($got, $expected, 'etiqueta')
Comparación profundais($got, hash { field k => 'v'; etc() }, 'etiqueta')
Probar excepciónlike(dies { ... }, qr/msg/, 'etiqueta')
Probar que no hay excepciónok(lives { ... }, 'etiqueta')
Simular un métodoTest::MockModule->new('Pkg')->mock(m => sub { ... })
Saltar pruebasSKIP: { skip 'razón', $count unless $cond; ... }
Pruebas TODOTODO: { local $TODO = 'razón'; ... }

Errores Comunes

Olvidar done_testing

# Mal: El archivo de prueba se ejecuta pero no verifica que todas las pruebas se ejecutaron
use Test2::V0;
is(1, 1, 'funciona');
# Falta done_testing — errores silenciosos si el código de prueba se salta

# Bien: Siempre terminar con done_testing
use Test2::V0;
is(1, 1, 'funciona');
done_testing;

Falta la Bandera -l

# Mal: Los módulos en lib/ no se encuentran
prove t/unit/user.t
# Can't locate MyApp/User.pm in @INC

# Bien: Incluir lib/ en @INC
prove -l t/unit/user.t

Sobremoquear

Simula la dependencia, no el código bajo prueba. Si tu prueba solo verifica que un mock devuelve lo que le indicaste, no prueba nada.

Contaminación de Pruebas

Usa variables my dentro de las subpruebas — nunca our — para evitar que el estado se filtre entre pruebas.

Recuerda: Las pruebas son tu red de seguridad. Mantenlas rápidas, enfocadas e independientes. Usa Test2::V0 para proyectos nuevos, prove para ejecutarlas y Devel::Cover para la responsabilidad.

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 relacionados