Tools
Script paths are relative to this skill's installed directory.
scripts/scaffold_test.pl -- Analyzes a .pm module and generates a
complete .t test file skeleton with proper imports, mocking, and subtests.
scripts/review_test.pl -- Audits an existing .t file against the
best practices checklist and reports pass/fail per item.
Both scripts accept --repo, --json, --verbose, and --help.
Generate Mode
Use when the user asks to write tests for a library module.
- Identify the module path. Ask if unclear. Must be relative to the
OSADO repo root (e.g.,
lib/mypackage/module.pm).
- Run the scaffold script:
perl scripts/scaffold_test.pl --repo /path/to/osado lib/mypackage/module.pm
This outputs a complete .t file to stdout. Use --output to write
directly to a file, or --json to get structured module info.
- Review and customize the output. The skeleton is a starting point:
- Verify the fake values are distinctive and traceable.
- Add conditional
script_output mocks if the function branches on
command output.
- Add assertions specific to the function's behavior (not just argument
presence).
- Ensure optional args have their own subtests.
- Write the file to
t/NN_<module_name>.t (the script suggests the
next available number).
- Run the test:
prove -v -l -Ios-autoinst/ t/NN_<module_name>.t
Review Mode
Use when the user asks to review or audit an existing test file.
- Run the review script:
perl scripts/review_test.pl --repo /path/to/osado t/NN_foo.t
- Present findings grouped as PASS/FAIL/WARN.
- For each failure, explain what's wrong and propose the fix, citing
the relevant section from
references/ut_rules.md.
- Optionally re-run after applying fixes to confirm the check passes.
Key Patterns (quick reference)
File skeleton
use strict;
use warnings;
use Test::More;
use Test::Exception;
use Test::Warnings;
use Test::MockModule;
use Test::Mock::Time;
use List::Util qw(any none uniq all)
use mypackage::module_name;
subtest '[function_name]' => sub { ... };
done_testing;
The @calls capture pattern
subtest '[function_name]' => sub {
my @calls;
my $mock = Test::MockModule->new('mypackage::module_name', no_auto => 1);
$mock->redefine(assert_script_run => sub { push @calls, $_[0]; return; });
$mock->redefine(record_info => sub { note(join(' ', 'RECORD_INFO -->', @_)); });
function_name(arg1 => 'Agamemnon', arg2 => 'Mycenaeans');
note("\n --> " . join("\n --> ", @calls));
ok((any { /expected_pattern/ } @calls), 'Descriptive assertion message');
};
Mandatory arg testing
subtest '[function_name] missing arguments' => sub {
dies_ok { function_name(arg2 => 'X') } 'Die for missing argument arg1';
dies_ok { function_name(arg1 => 'X') } 'Die for missing argument arg2';
};
Rules
- Always use
no_auto => 1 in Test::MockModule constructors.
- Always use
redefine(), never mock().
- Each subtest must be self-contained: own
@calls, own mocks, no shared state.
- Always mock
record_info (redirect to note).
- Clean up
set_var with undef at end of subtests.
- Use distinctive fake values (mythology, Italian, mushrooms) -- never
"foo", "bar", "test".
- Test behavior (what commands are generated), not implementation (internal
call order).
- Assertion messages must be specific, unique, and informative on failure.
- Use regex matching (
any { /pattern/ } @calls) not exact string equality
for command assertions.
- Do NOT modify the library code -- this skill only creates/edits test files.
- After generating tests, suggest running them via
local-lint-test or
directly with prove.
1---2name: unit-test-wizard3description: Writes and reviews OSADO Perl unit tests for library modules in lib/. Activate when the user asks to "write a test", "add unit tests", "scaffold a test file", "review this test", "check test quality", or needs help with Test::MockModule, dies_ok assertions, or subtest structure.4---56<instructions>7You help an OSADO developer write and review unit tests for Perl library8modules. Tests follow established patterns documented in9`references/ut_rules.md` -- read it when you need the full pattern catalog.1011## Tools1213Script paths are relative to this skill's installed directory.1415* `scripts/scaffold_test.pl` -- Analyzes a `.pm` module and generates a16 complete `.t` test file skeleton with proper imports, mocking, and subtests.17* `scripts/review_test.pl` -- Audits an existing `.t` file against the18 best practices checklist and reports pass/fail per item.1920Both scripts accept `--repo`, `--json`, `--verbose`, and `--help`.2122## Generate Mode2324Use when the user asks to write tests for a library module.25261. **Identify the module path.** Ask if unclear. Must be relative to the27 OSADO repo root (e.g., `lib/mypackage/module.pm`).282. **Run the scaffold script:**29 ```bash30 perl scripts/scaffold_test.pl --repo /path/to/osado lib/mypackage/module.pm31 ```32 This outputs a complete `.t` file to stdout. Use `--output` to write33 directly to a file, or `--json` to get structured module info.343. **Review and customize the output.** The skeleton is a starting point:35 * Verify the fake values are distinctive and traceable.36 * Add conditional `script_output` mocks if the function branches on37 command output.38 * Add assertions specific to the function's behavior (not just argument39 presence).40 * Ensure optional args have their own subtests.414. **Write the file** to `t/NN_<module_name>.t` (the script suggests the42 next available number).435. **Run the test:**44 ```bash45 prove -v -l -Ios-autoinst/ t/NN_<module_name>.t46 ```4748## Review Mode4950Use when the user asks to review or audit an existing test file.51521. **Run the review script:**53 ```bash54 perl scripts/review_test.pl --repo /path/to/osado t/NN_foo.t55 ```562. **Present findings** grouped as PASS/FAIL/WARN.573. **For each failure**, explain what's wrong and propose the fix, citing58 the relevant section from `references/ut_rules.md`.594. **Optionally re-run** after applying fixes to confirm the check passes.6061## Key Patterns (quick reference)6263### File skeleton64```perl65use strict;66use warnings;67use Test::More;68use Test::Exception;69use Test::Warnings;70use Test::MockModule;71use Test::Mock::Time;72use List::Util qw(any none uniq all)7374use mypackage::module_name;7576subtest '[function_name]' => sub { ... };7778done_testing;79```8081### The @calls capture pattern82```perl83subtest '[function_name]' => sub {84 my @calls;85 my $mock = Test::MockModule->new('mypackage::module_name', no_auto => 1);86 $mock->redefine(assert_script_run => sub { push @calls, $_[0]; return; });87 $mock->redefine(record_info => sub { note(join(' ', 'RECORD_INFO -->', @_)); });8889 function_name(arg1 => 'Agamemnon', arg2 => 'Mycenaeans');9091 note("\n --> " . join("\n --> ", @calls));92 ok((any { /expected_pattern/ } @calls), 'Descriptive assertion message');93};94```9596### Mandatory arg testing97```perl98subtest '[function_name] missing arguments' => sub {99 dies_ok { function_name(arg2 => 'X') } 'Die for missing argument arg1';100 dies_ok { function_name(arg1 => 'X') } 'Die for missing argument arg2';101};102```103104## Rules105106* Always use `no_auto => 1` in Test::MockModule constructors.107* Always use `redefine()`, never `mock()`.108* Each subtest must be self-contained: own `@calls`, own mocks, no shared state.109* Always mock `record_info` (redirect to `note`).110* Clean up `set_var` with `undef` at end of subtests.111* Use distinctive fake values (mythology, Italian, mushrooms) -- never112 "foo", "bar", "test".113* Test behavior (what commands are generated), not implementation (internal114 call order).115* Assertion messages must be specific, unique, and informative on failure.116* Use regex matching (`any { /pattern/ } @calls`) not exact string equality117 for command assertions.118* Do NOT modify the library code -- this skill only creates/edits test files.119* After generating tests, suggest running them via `local-lint-test` or120 directly with `prove`.121</instructions>