首页/测试 & QA/perl-testing
P

perl-testing

by @affaan-mv1.0.0
0.0(0)

提供Perl应用程序的全面测试策略,利用Test::More等工具,确保代码质量和功能稳定性。

Perl TestingTest AutomationQuality AssuranceSoftware TestingGitHub
安装方式
npx skills add affaan-m/everything-claude-code --skill perl-testing
compare_arrows

Before / After 效果对比

1
使用前

Perl应用缺乏全面测试,代码质量不稳定,易出现Bug。

使用后

采用全面测试策略,确保Perl应用质量,减少Bug。

description SKILL.md

perl-testing

Perl Testing Patterns Comprehensive testing strategies for Perl applications using Test2::V0, Test::More, prove, and TDD methodology. When to Activate Writing new Perl code (follow TDD: red, green, refactor) Designing test suites for Perl modules or applications Reviewing Perl test coverage Setting up Perl testing infrastructure Migrating tests from Test::More to Test2::V0 Debugging failing Perl tests TDD Workflow Always follow the RED-GREEN-REFACTOR cycle. # Step 1: RED — Write a failing test # 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, 'adds two numbers'); is($calc->add(-1, 1), 0, 'handles negatives'); }; done_testing; # Step 2: GREEN — Write minimal implementation # lib/Calculator.pm package Calculator; use v5.36; use Moo; sub add($self, $a, $b) { return $a + $b; } 1; # Step 3: REFACTOR — Improve while tests stay green # Run: prove -lv t/unit/calculator.t Test::More Fundamentals The standard Perl testing module — widely used, ships with core. Basic Assertions use v5.36; use Test::More; # Plan upfront or use done_testing # plan tests => 5; # Fixed plan (optional) # Equality is($result, 42, 'returns correct value'); isnt($result, 0, 'not zero'); # Boolean ok($user->is_active, 'user is active'); ok(!$user->is_banned, 'user is not banned'); # Deep comparison is_deeply( $got, { name => 'Alice', roles => ['admin'] }, 'returns expected structure' ); # Pattern matching like($error, qr/not found/i, 'error mentions not found'); unlike($output, qr/password/, 'output hides password'); # Type check isa_ok($obj, 'MyApp::User'); can_ok($obj, 'save', 'delete'); done_testing; SKIP and TODO use v5.36; use Test::More; # Skip tests conditionally SKIP: { skip 'No database configured', 2 unless $ENV{TEST_DB}; my $db = connect_db(); ok($db->ping, 'database is reachable'); is($db->version, '15', 'correct PostgreSQL version'); } # Mark expected failures TODO: { local $TODO = 'Caching not yet implemented'; is($cache->get('key'), 'value', 'cache returns value'); } done_testing; Test2::V0 Modern Framework Test2::V0 is the modern replacement for Test::More — richer assertions, better diagnostics, and extensible. Why Test2? Superior deep comparison with hash/array builders Better diagnostic output on failures Subtests with cleaner scoping Extensible via Test2::Tools::* plugins Backward-compatible with Test::More tests Deep Comparison with Builders use v5.36; use Test2::V0; # Hash builder — check partial structure is( $user->to_hash, hash { field name => 'Alice'; field email => match(qr/@example.com$/); field age => validator(sub { $_ >= 18 }); # Ignore other fields etc(); }, 'user has expected fields' ); # Array builder is( $result, array { item 'first'; item match(qr/^second/); item DNE(); # Does Not Exist — verify no extra items }, 'result matches expected list' ); # Bag — order-independent comparison is( $tags, bag { item 'perl'; item 'testing'; item 'tdd'; }, 'has all required tags regardless of order' ); Subtests use v5.36; use Test2::V0; subtest 'User creation' => sub { my $user = User->new(name => 'Alice', email => 'alice@example.com'); ok($user, 'user object created'); is($user->name, 'Alice', 'name is set'); is($user->email, 'alice@example.com', 'email is set'); }; subtest 'User validation' => sub { my $warnings = warns { User->new(name => '', email => 'bad'); }; ok($warnings, 'warns on invalid data'); }; done_testing; Exception Testing with Test2 use v5.36; use Test2::V0; # Test that code dies like( dies { divide(10, 0) }, qr/Division by zero/, 'dies on division by zero' ); # Test that code lives ok(lives { divide(10, 2) }, 'division succeeds') or note($@); # Combined pattern subtest 'error handling' => sub { ok(lives { parse_config('valid.json') }, 'valid config parses'); like( dies { parse_config('missing.json') }, qr/Cannot open/, 'missing file dies with message' ); }; done_testing; Test Organization and prove Directory Structure t/ ├── 00-load.t # Verify modules compile ├── 01-basic.t # Core functionality ├── unit/ │ ├── config.t # Unit tests by module │ ├── user.t │ └── util.t ├── integration/ │ ├── database.t │ └── api.t ├── lib/ │ └── TestHelper.pm # Shared test utilities └── fixtures/ ├── config.json # Test data files └── users.csv prove Commands # Run all tests prove -l t/ # Verbose output prove -lv t/ # Run specific test prove -lv t/unit/user.t # Recursive search prove -lr t/ # Parallel execution (8 jobs) prove -lr -j8 t/ # Run only failing tests from last run prove -l --state=failed t/ # Colored output with timer prove -l --color --timer t/ # TAP output for CI prove -l --formatter TAP::Formatter::JUnit t/ > results.xml .proverc Configuration -l --color --timer -r -j4 --state=save Fixtures and Setup/Teardown Subtest Isolation use v5.36; use Test2::V0; use File::Temp qw(tempdir); use Path::Tiny; subtest 'file processing' => sub { # Setup my $dir = tempdir(CLEANUP => 1); my $file = path($dir, 'input.txt'); $file->spew_utf8("line1\nline2\nline3\n"); # Test my $result = process_file("$file"); is($result->{line_count}, 3, 'counts lines'); # Teardown happens automatically (CLEANUP => 1) }; Shared Test Helpers Place reusable helpers in t/lib/TestHelper.pm and load with use lib 't/lib'. Export factory functions like create_test_db(), create_temp_dir(), and fixture_path() via Exporter. Mocking Test::MockModule use v5.36; use Test2::V0; use Test::MockModule; subtest 'mock external API' => sub { my $mock = Test::MockModule->new('MyApp::API'); # Good: Mock returns controlled data $mock->mock(fetch_user => sub ($self, $id) { return { id => $id, name => 'Mock User', email => 'mock@test.com' }; }); my $api = MyApp::API->new; my $user = $api->fetch_user(42); is($user->{name}, 'Mock User', 'returns mocked user'); # Verify call count 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 called twice'); # Mock is automatically restored when $mock goes out of scope }; # Bad: Monkey-patching without restoration # MyApp::API::fetch_user = sub { ... }; # NEVER — leaks across tests For lightweight mock objects, use Test::MockObject to create injectable test doubles with ->mock() and verify calls with ->called_ok(). Coverage with Devel::Cover Running Coverage # Basic coverage report cover -test # Or step by step perl -MDevel::Cover -Ilib t/unit/user.t cover # HTML report cover -report html open cover_db/coverage.html # Specific thresholds cover -test -report text | grep 'Total' # CI-friendly: fail under threshold cover -test && cover -report text -select '^lib/' \ | perl -ne 'if (/Total.?(\d+.\d+)/) { exit 1 if $1 < 80 }' Integration Testing Use in-memory SQLite for database tests, mock HTTP::Tiny for API tests. use v5.36; use Test2::V0; use DBI; subtest 'database integration' => 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', 'inserted and retrieved user'); }; done_testing; Best Practices DO Follow TDD: Write tests before implementation (red-green-refactor) Use Test2::V0: Modern assertions, better diagnostics Use subtests: Group related assertions, isolate state Mock external dependencies: Network, database, file system Use prove -l: Always include lib/ in @INC Name tests clearly: 'user login with invalid password fails' Test edge cases: Empty strings, undef, zero, boundary values Aim for 80%+ coverage: Focus on business logic paths Keep tests fast: Mock I/O, use in-memory databases DON'T Don't test implementation: Test behavior and output, not internals Don't share state between subtests: Each subtest should be independent Don't skip done_testing: Ensures all planned tests ran Don't over-mock: Mock boundaries only, not the code under test Don't use Test::More for new projects: Prefer Test2::V0 Don't ignore test failures: All tests must pass before merge Don't test CPAN modules: Trust libraries to work correctly Don't write brittle tests: Avoid over-specific string matching Quick Reference Task Command / Pattern Run all tests prove -lr t/ Run one test verbose prove -lv t/unit/user.t Parallel test run prove -lr -j8 t/ Coverage report cover -test && cover -report html Test equality is($got, $expected, 'label') Deep comparison is($got, hash { field k => 'v'; etc() }, 'label') Test exception like(dies { ... }, qr/msg/, 'label') Test no exception ok(lives { ... }, 'label') Mock a method Test::MockModule->new('Pkg')->mock(m => sub { ... }) Skip tests SKIP: { skip 'reason', $count unless $cond; ... } TODO tests TODO: { local $TODO = 'reason'; ... } Common Pitfalls Forgetting done_testing # Bad: Test file runs but doesn't verify all tests executed use Test2::V0; is(1, 1, 'works'); # Missing done_testing — silent bugs if test code is skipped # Good: Always end with done_testing use Test2::V0; is(1, 1, 'works'); done_testing; Missing -l Flag # Bad: Modules in lib/ not found prove t/unit/user.t # Can't locate MyApp/User.pm in @INC # Good: Include lib/ in @INC prove -l t/unit/user.t Over-Mocking Mock the dependency, not the code under test. If your test only verifies that a mock returns what you told it to, it tests nothing. Test Pollution Use my variables inside subtests — never our — to prevent state leaking between tests. Remember: Tests are your safety net. Keep them fast, focused, and independent. Use Test2::V0 for new projects, prove for running, and Devel::Cover for accountability.Weekly Installs209Repositoryaffaan-m/everyt…ude-codeGitHub Stars80.5KFirst Seen6 days agoSecurity AuditsGen Agent Trust HubPassSocketPassSnykPassInstalled oncodex197cursor164kimi-cli163gemini-cli163amp163cline163

forum用户评价 (0)

发表评价

效果
易用性
文档
兼容性

暂无评价,来写第一条吧

统计数据

安装量0
评分0.0 / 5.0
版本1.0.0
更新日期2026年3月18日
对比案例1 组

用户评分

0.0(0)
5
0%
4
0%
3
0%
2
0%
1
0%

为此 Skill 评分

0.0

兼容平台

🔧Claude Code

时间线

创建2026年3月18日
最后更新2026年3月18日