Communitygithub.com

perl-testing

Padrões de teste em Perl usando Test2::V0, Test::More, prove runner, mocks, cobertura com Devel::Cover e metodologia TDD. Ative ao escrever testes Perl com Test2::V0 ou Test::More, ou ao medir cobertura.

O que é perl-testing?

Este skill fornece um guia completo e prático para testes em Perl, cobrindo desde os fundamentos com Test::More até a moderna suíte Test2::V0, passando por organização de testes com prove, mocking, cobertura de código com Devel::Cover, e a metodologia TDD (Red-Green-Refactor). Ele é ideal para desenvolvedores que desejam escrever testes robustos e confiáveis em projetos Perl, seja para novos módulos ou para manutenção de bases de código existentes. O skill é compatível com qualquer ambiente Perl (como Claude Code, Cursor ou Codex) e oferece exemplos concretos de código, comandos de terminal e boas práticas. Ao seguir as diretrizes apresentadas, o assistente consegue gerar testes seguindo padrões profissionais, evitando armadilhas comuns e garantindo alta cobertura e manutenibilidade.

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

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Padrões de Teste em Perl

Estratégias abrangentes de teste para aplicações Perl usando Test2::V0, Test::More, prove e metodologia TDD.

Quando Ativar

  • Escrevendo novo código Perl (siga TDD: vermelho, verde, refatorar)
  • Projetando suítes de teste para módulos ou aplicações Perl
  • Revisando cobertura de teste Perl
  • Configurando infraestrutura de teste Perl
  • Migrando testes de Test::More para Test2::V0
  • Depurando testes Perl com falha

Fluxo de Trabalho TDD

Sempre siga o ciclo VERMELHO-VERDE-REFATORAR.

# Passo 1: VERMELHO — Escreva um teste com falha
# t/unit/calculator.t
use v5.36;
use Test2::V0;

use lib 'lib';
use Calculator;

subtest 'adição' => sub {
    my $calc = Calculator->new;
    is($calc->add(2, 3), 5, 'soma dois números');
    is($calc->add(-1, 1), 0, 'lida com negativos');
};

done_testing;

# Passo 2: VERDE — Escreva implementação mínima
# lib/Calculator.pm
package Calculator;
use v5.36;
use Moo;

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

1;

# Passo 3: REFATORAR — Melhore enquanto os testes permanecem verdes
# Execute: prove -lv t/unit/calculator.t

Fundamentos do Test::More

O módulo de teste padrão do Perl — amplamente utilizado, acompanha o core.

Asserções Básicas

use v5.36;
use Test::More;

# Planeje com antecedência ou use done_testing
# plan tests => 5;  # Plano fixo (opcional)

# Igualdade
is($resultado, 42, 'retorna valor correto');
isnt($resultado, 0, 'não é zero');

# Booleano
ok($usuario->is_active, 'usuário está ativo');
ok(!$usuario->is_banned, 'usuário não está banido');

# Comparação profunda
is_deeply(
    $obtido,
    { nome => 'Alice', papeis => ['admin'] },
    'retorna estrutura esperada'
);

# Correspondência de padrão
like($erro, qr/não encontrado/i, 'erro menciona não encontrado');
unlike($saida, qr/senha/, 'saída oculta senha');

# Verificação de tipo
isa_ok($obj, 'MyApp::User');
can_ok($obj, 'salvar', 'deletar');

done_testing;

SKIP e TODO

use v5.36;
use Test::More;

# Pular testes condicionalmente
SKIP: {
    skip 'Nenhum banco de dados configurado', 2 a menos que $ENV{TEST_DB};

    my $db = connect_db();
    ok($db->ping, 'banco de dados está acessível');
    is($db->version, '15', 'versão correta do PostgreSQL');
}

# Marcar falhas esperadas
TODO: {
    local $TODO = 'Cache ainda não implementado';
    is($cache->get('chave'), 'valor', 'cache retorna valor');
}

done_testing;

Framework Moderno Test2::V0

Test2::V0 é o substituto moderno para Test::More — asserções mais ricas, melhores diagnósticos e extensível.

Por que Test2?

  • Comparação profunda superior com construtores hash/array
  • Melhor saída de diagnóstico em falhas
  • Subtests com escopo mais limpo
  • Extensível via plugins Test2::Tools::*
  • Compatível com versões anteriores de testes Test::More

Comparação Profunda com Construtores

use v5.36;
use Test2::V0;

# Construtor hash — verificar estrutura parcial
is(
    $usuario->to_hash,
    hash {
        field nome  => 'Alice';
        field email => match(qr/\@example\.com$/);
        field idade   => validator(sub { $_ >= 18 });
        # Ignorar outros campos
        etc();
    },
    'usuário tem campos esperados'
);

# Construtor array
is(
    $resultado,
    array {
        item 'primeiro';
        item match(qr/^segundo/);
        item DNE();  # Does Not Exist — verificar se não há itens extras
    },
    'resultado corresponde à lista esperada'
);

# Bag — comparação independente de ordem
is(
    $tags,
    bag {
        item 'perl';
        item 'teste';
        item 'tdd';
    },
    'tem todas as tags necessárias independente da ordem'
);

Subtests

use v5.36;
use Test2::V0;

subtest 'Criação de Usuário' => sub {
    my $usuario = User->new(nome => 'Alice', email => '[email protected]');
    ok($usuario, 'objeto de usuário criado');
    is($usuario->nome, 'Alice', 'nome está definido');
    is($usuario->email, '[email protected]', 'email está definido');
};

subtest 'Validação de Usuário' => sub {
    my $avisos = warns {
        User->new(nome => '', email => 'ruim');
    };
    ok($avisos, 'avisa sobre dados inválidos');
};

done_testing;

Teste de Exceções com Test2

use v5.36;
use Test2::V0;

# Testar se o código morre
like(
    dies { divide(10, 0) },
    qr/Divisão por zero/,
    'morre na divisão por zero'
);

# Testar se o código vive
ok(lives { divide(10, 2) }, 'divisão bem-sucedida') or note($@);

# Padrão combinado
subtest 'tratamento de erros' => sub {
    ok(lives { parse_config('válido.json') }, 'configuração válida é analisada');
    like(
        dies { parse_config('faltando.json') },
        qr/Não é possível abrir/,
        'arquivo faltando morre com mensagem'
    );
};

done_testing;

Organização de Testes e prove

Estrutura de Diretórios

t/
├── 00-load.t              # Verificar compilação dos módulos
├── 01-basic.t             # Funcionalidade principal
├── unit/
│   ├── config.t           # Testes unitários por módulo
│   ├── user.t
│   └── util.t
├── integration/
│   ├── database.t
│   └── api.t
├── lib/
│   └── TestHelper.pm      # Utilitários de teste compartilhados
└── fixtures/
    ├── config.json        # Arquivos de dados de teste
    └── users.csv

Comandos prove

# Executar todos os testes
prove -l t/

# Saída detalhada
prove -lv t/

# Executar teste específico
prove -lv t/unit/user.t

# Busca recursiva
prove -lr t/

# Execução paralela (8 jobs)
prove -lr -j8 t/

# Executar apenas testes com falha da última execução
prove -l --state=failed t/

# Saída colorida com timer
prove -l --color --timer t/

# Saída TAP para CI
prove -l --formatter TAP::Formatter::JUnit t/ > resultados.xml

Configuração .proverc

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

Fixtures e Setup/Teardown

Isolamento de Subtest

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

subtest 'processamento de arquivo' => sub {
    # Setup
    my $dir = tempdir(CLEANUP => 1);
    my $arquivo = path($dir, 'entrada.txt');
    $arquivo->spew_utf8("linha1\nlinha2\nlinha3\n");

    # Teste
    my $resultado = process_file("$arquivo");
    is($resultado->{contagem_linhas}, 3, 'conta linhas');

    # Teardown ocorre automaticamente (CLEANUP => 1)
};

Helpers de Teste Compartilhados

Coloque helpers reutilizáveis em t/lib/TestHelper.pm e carregue com use lib 't/lib'. Exporte funções de fábrica como create_test_db(), create_temp_dir() e fixture_path() via Exporter.

Mocking

Test::MockModule

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

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

    # Bom: Mock retorna dados controlados
    $mock->mock(fetch_user => sub ($self, $id) {
        return { id => $id, nome => 'Usuário Mock', email => '[email protected]' };
    });

    my $api = MyApp::API->new;
    my $usuario = $api->fetch_user(42);
    is($usuario->{nome}, 'Usuário Mock', 'retorna usuário mockado');

    # Verificar contagem de chamadas
    my $contagem_chamadas = 0;
    $mock->mock(fetch_user => sub { $contagem_chamadas++; return {} });
    $api->fetch_user(1);
    $api->fetch_user(2);
    is($contagem_chamadas, 2, 'fetch_user chamado duas vezes');

    # Mock é automaticamente restaurado quando $mock sai de escopo
};

# Ruim: Monkey-patching sem restauração
# *MyApp::API::fetch_user = sub { ... };  # NUNCA — vaza entre testes

Para objetos mock leves, use Test::MockObject para criar dublês de teste injetáveis com ->mock() e verifique chamadas com ->called_ok().

Cobertura com Devel::Cover

Executando Cobertura

# Relatório básico de cobertura
cover -test

# Ou passo a passo
perl -MDevel::Cover -Ilib t/unit/user.t
cover

# Relatório HTML
cover -report html
open cover_db/coverage.html

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

# Amigável para CI: falhar abaixo do limiar
cover -test && cover -report text -select '^lib/' \
  | perl -ne 'if (/Total.*?(\d+\.\d+)/) { exit 1 if $1 < 80 }'

Testes de Integração

Use SQLite em memória para testes de banco de dados, mock HTTP::Tiny para testes de API.

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

subtest 'integração com banco de dados' => sub {
    my $dbh = DBI->connect('dbi:SQLite:dbname=:memory:', '', '', {
        RaiseError => 1,
    });
    $dbh->do('CREATE TABLE usuarios (id INTEGER PRIMARY KEY, nome TEXT)');

    $dbh->prepare('INSERT INTO usuarios (nome) VALUES (?)')->execute('Alice');
    my $linha = $dbh->selectrow_hashref('SELECT * FROM usuarios WHERE nome = ?', undef, 'Alice');
    is($linha->{nome}, 'Alice', 'usuário inserido e recuperado');
};

done_testing;

Melhores Práticas

FAÇA

  • Siga TDD: Escreva testes antes da implementação (vermelho-verde-refatorar)
  • Use Test2::V0: Asserções modernas, melhores diagnósticos
  • Use subtests: Agrupe asserções relacionadas, isole estado
  • Mock dependências externas: Rede, banco de dados, sistema de arquivos
  • Use prove -l: Sempre inclua lib/ em @INC
  • Nomeie testes claramente: 'login do usuário com senha inválida falha'
  • Teste casos de borda: Strings vazias, undef, zero, valores limite
  • Mire em cobertura de 80%+: Foque em caminhos de lógica de negócio
  • Mantenha testes rápidos: Mock I/O, use bancos de dados em memória

NÃO FAÇA

  • Não teste implementação: Teste comportamento e saída, não internos
  • Não compartilhe estado entre subtests: Cada subtest deve ser independente
  • Não pule done_testing: Garante que todos os testes planejados foram executados
  • Não exagere no mock: Mock apenas fronteiras, não o código sob teste
  • Não use Test::More para novos projetos: Prefira Test2::V0
  • Não ignore falhas de teste: Todos os testes devem passar antes do merge
  • Não teste módulos CPAN: Confie que as bibliotecas funcionam corretamente
  • Não escreva testes frágeis: Evite correspondência de string excessivamente específica

Referência Rápida

TarefaComando / Padrão
Executar todos os testesprove -lr t/
Executar um teste detalhadoprove -lv t/unit/user.t
Execução paralela de testesprove -lr -j8 t/
Relatório de coberturacover -test && cover -report html
Testar igualdadeis($obtido, $esperado, 'rótulo')
Comparação profundais($obtido, hash { field k => 'v'; etc() }, 'rótulo')
Testar exceçãolike(dies { ... }, qr/msg/, 'rótulo')
Testar sem exceçãook(lives { ... }, 'rótulo')
Mock de métodoTest::MockModule->new('Pkg')->mock(m => sub { ... })
Pular testesSKIP: { skip 'motivo', $count a menos que $cond; ... }
Testes TODOTODO: { local $TODO = 'motivo'; ... }

Armadilhas Comuns

Esquecer done_testing

# Ruim: O arquivo de teste é executado mas não verifica se todos os testes foram executados
use Test2::V0;
is(1, 1, 'funciona');
# Faltando done_testing — bugs silenciosos se o código de teste for pulado

# Bom: Sempre termine com done_testing
use Test2::V0;
is(1, 1, 'funciona');
done_testing;

Flag -l Faltando

# Ruim: Módulos em lib/ não encontrados
prove t/unit/user.t
# Can't locate MyApp/User.pm in @INC

# Bom: Incluir lib/ em @INC
prove -l t/unit/user.t

Excesso de Mock

Mock a dependência, não o código sob teste. Se seu teste apenas verifica que um mock retorna o que você disse a ele, ele não testa nada.

Poluição de Teste

Use variáveis my dentro de subtests — nunca our — para evitar vazamento de estado entre testes.

Lembre-se: Testes são sua rede de segurança. Mantenha-os rápidos, focados e independentes. Use Test2::V0 para novos projetos, prove para execução e Devel::Cover para responsabilidade.

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.

Habilidades Relacionadas