Communitygithub.com

perl-testing

Patrones de pruebas en Perl usando Test2::V0, Test::More, prove, 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?

Esta skill proporciona una guía completa y estructurada para implementar pruebas en Perl, abarcando desde los fundamentos con Test::More hasta el framework moderno Test2::V0. Incluye flujos de trabajo TDD (red-green-refactor), organización de tests con prove, estrategias de mocking con Test::MockModule y Test::MockObject, y medición de cobertura con Devel::Cover. La skill detalla cómo estructurar suites de pruebas con directorios organizados, utilizar subtests para aislamiento, manejar fixtures y configuraciones de entorno, y seguir las mejores prácticas para evitar errores comunes como olvidar done_testing o sobre-mockear dependencias. Es especialmente útil para desarrolladores que necesitan escribir nuevas pruebas, migrar de Test::More a Test2::V0, o garantizar una cobertura de código superior al 80% en sus proyectos Perl.

Compatible con~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/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 Pruebas en Perl

Estrategias exhaustivas para pruebas en aplicaciones Perl usando Test2::V0, Test::More, prove y metodología TDD.

Cuándo activarlo

  • Escribir código Perl nuevo (sigue TDD: rojo, verde, refactorizar)
  • Diseñar suites de prueba para módulos o aplicaciones Perl
  • Revisar la cobertura de pruebas Perl
  • Configurar infraestructura de pruebas Perl
  • Migrar pruebas de Test::More a Test2::V0
  • 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 'adición' => 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 pruebas estándar de Perl — ampliamente usado, incluido en el núcleo.

Aserciones básicas

use v5.36;
use Test::More;

# Plan anticipado o usar done_testing
# plan tests => 5;  # Plan fijo (opcional)

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

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

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

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

# Verificación de tipo
isa_ok($obj, 'MiApp::Usuario');
can_ok($obj, 'guardar', 'eliminar');

done_testing;

SKIP y TODO

use v5.36;
use Test::More;

# Saltar pruebas condicionalmente
SKIP: {
    skip 'No hay base de datos configurada', 2 a menos que $ENV{DB_PRUEBAS};

    my $db = conectar_bd();
    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('clave'), 'valor', 'la caché devuelve el valor');
}

done_testing;

Test2::V0 Framework Moderno

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

¿Por qué Test2?

  • Comparación profunda superior con constructores hash/array
  • Mejor salida de diagnóstico en fallos
  • Subtests 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 hash — verifica estructura parcial
is(
    $usuario->to_hash,
    hash {
        field nombre  => 'Ana';
        field email => match(qr/\@ejemplo\.com$/);
        field edad   => validator(sub { $_ >= 18 });
        # Ignorar otros campos
        etc();
    },
    'el usuario tiene los campos esperados'
);

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

# Bag — comparación sin importar el orden
is(
    $etiquetas,
    bag {
        item 'perl';
        item 'pruebas';
        item 'tdd';
    },
    'tiene todas las etiquetas requeridas sin importar el orden'
);

Subtests

use v5.36;
use Test2::V0;

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

subtest 'Validación de usuario' => sub {
    my $avisos = warns {
        Usuario->new(nombre => '', email => 'malo');
    };
    ok($avisos, 'advierte sobre datos inválidos');
};

done_testing;

Pruebas de excepciones con Test2

use v5.36;
use Test2::V0;

# Probar que el código muere
like(
    dies { dividir(10, 0) },
    qr/División por cero/,
    'muere en división por cero'
);

# Probar que el código vive
ok(lives { dividir(10, 2) }, 'la división tiene éxito') o note($@);

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

done_testing;

Organización de pruebas y prove

Estructura de directorios

t/
├── 00-carga.t              # Verifica que los módulos compilan
├── 01-basico.t             # Funcionalidad principal
├── unit/
│   ├── config.t            # Pruebas unitarias por módulo
│   ├── usuario.t
│   └── util.t
├── integration/
│   ├── base_de_datos.t
│   └── api.t
├── lib/
│   └── AyudantePruebas.pm  # Utilidades compartidas para pruebas
└── fixtures/
    ├── config.json        # Archivos de datos de prueba
    └── usuarios.csv

Comandos prove

# Ejecutar todas las pruebas
prove -l t/

# Salida verbose
prove -lv t/

# Ejecutar una prueba específica
prove -lv t/unit/usuario.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/ > resultados.xml

Configuración .proverc

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

Fixtures y Configuración/Limpieza

Aislamiento con subtests

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

subtest 'procesamiento de archivos' => sub {
    # Configuración
    my $dir = tempdir(LIMPIEZA => 1);
    my $archivo = path($dir, 'entrada.txt');
    $archivo->spew_utf8("línea1\nlínea2\nlínea3\n");

    # Prueba
    my $resultado = procesar_archivo("$archivo");
    is($resultado->{conteo_lineas}, 3, 'cuenta líneas');

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

Ayudantes de prueba compartidos

Coloca ayudantes reutilizables en t/lib/AyudantePruebas.pm y cárgalos con use lib 't/lib'. Exporta funciones fábrica como crear_bd_prueba(), crear_dir_temporal(), y ruta_fixture() mediante Exporter.

Mocking

Test::MockModule

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

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

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

    my $api = MiApp::API->new;
    my $usuario = $api->obtener_usuario(42);
    is($usuario->{nombre}, 'Usuario Simulado', 'devuelve usuario simulado');

    # Verificar conteo de llamadas
    my $conteo_llamadas = 0;
    $mock->mock(obtener_usuario => sub { $conteo_llamadas++; return {} });
    $api->obtener_usuario(1);
    $api->obtener_usuario(2);
    is($conteo_llamadas, 2, 'obtener_usuario fue llamado dos veces');

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

# Mal: Monkey-patching sin restauración
# *MiApp::API::obtener_usuario = 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

# Reporte básico de cobertura
cover -test

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

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

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

# Amigable para CI: falla si está bajo el 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 usuarios (id INTEGER PRIMARY KEY, nombre TEXT)');

    $dbh->prepare('INSERT INTO usuarios (nombre) VALUES (?)')->execute('Ana');
    my $fila = $dbh->selectrow_hashref('SELECT * FROM usuarios WHERE nombre = ?', undef, 'Ana');
    is($fila->{nombre}, 'Ana', 'usuario insertado y recuperado');
};

done_testing;

Buenas prácticas

SÍ HACER

  • Seguir TDD: Escribe las pruebas antes de la implementación (rojo-verde-refactorizar)
  • Usar Test2::V0: Aserciones modernas, mejor diagnóstico
  • Usar subtests: Agrupa aserciones relacionadas, aísla el estado
  • Simular dependencias externas: Red, base de datos, sistema de archivos
  • Usar prove -l: Incluye siempre lib/ en @INC
  • Nombrar pruebas claramente: '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: Enfócate en las rutas de lógica de negocio
  • Mantener pruebas rápidas: Simula E/S, usa bases de datos en memoria

NO HACER

  • No probar la implementación: Prueba comportamiento y salida, no detalles internos
  • No compartir estado entre subtests: Cada subtest debe ser independiente
  • No omitir done_testing: Asegura que todas las pruebas planificadas se ejecutaron
  • No sobresimular: Simula solo los límites, no el código bajo prueba
  • No usar Test::More para proyectos nuevos: Prefiere Test2::V0
  • No ignorar fallos en pruebas: Todas las pruebas deben pasar antes de fusionar
  • No probar módulos CPAN: Confía en que las bibliotecas funcionan correctamente
  • No escribir pruebas frágiles: Evita coincidencias de cadenas demasiado específicas

Referencia rápida

TareaComando / Patrón
Ejecutar todas las pruebasprove -lr t/
Ejecutar una prueba verboseprove -lv t/unit/usuario.t
Ejecución paralelaprove -lr -j8 t/
Reporte de coberturacover -test && cover -report html
Probar igualdadis($obtenido, $esperado, 'etiqueta')
Comparación profundais($obtenido, hash { field k => 'v'; etc() }, 'etiqueta')
Probar excepciónlike(dies { ... }, qr/msg/, 'etiqueta')
Probar sin excepciónok(lives { ... }, 'etiqueta')
Simular un métodoTest::MockModule->new('Pkg')->mock(m => sub { ... })
Saltar pruebasSKIP: { skip 'razón', $cuenta a menos que $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 ejecuten
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 el flag -l

# Mal: Módulos en lib/ no encontrados
prove t/unit/usuario.t
# No se puede localizar MiApp/Usuario.pm en @INC

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

Sobresimulación

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

Contaminación de pruebas

Usa variables my dentro de los subtests — 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 ejecutar 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/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 relacionados