Describe a routine once. Get the tests you would not write by hand.
App::Test::Generator takes a formal input/output specification for one
routine and emits a runnable test file: random fuzzing shaped by the
declared types, deterministic boundary cases for every min,
max and len, the static cases you list, and
optional property-based tests. The same schemas then feed mutation
testing, benchmarks and an HTML dashboard.
- version 0.47
- Perl 5.036+
- GPL-2.0
- ten commands
- no runtime service
cpanm App::Test::Generator
# a schema for one routine: Perl's abs()
cat > t/conf/abs.yml <<'EOF'
---
module: builtin
function: abs
input:
number:
type: number
position: 0
output:
type: number
min: 0
EOF
# generate the harness, run it, print the TAP result
fuzz-harness-generator -r t/conf/abs.yml
The output is an ordinary .t file built on
Test::Most. It validates
its own generated inputs with
Params::Validate::Strict
and the routine's return value with
Return::Set, so a test
fails when the routine returns the wrong type or breaks a documented
bound — not only when it dies.
Six subsystems, one schema format
-
Fuzz tester
Random and boundary inputs by declared type, with configurable iterations, edge-case pools and a reproducible seed.
-
Schema extraction
Reads a
.pmfile and infers parameter schemas from signatures, validation calls, POD and formal spec blocks. -
Mutation testing
Four operators, per-file survivors, and a feedback loop that turns surviving mutants into new boundary tests.
-
Test dashboard
Coverage, LCSAJ path metrics, mutation results, duplication and CPAN Testers failures in one static HTML report.
-
Benchmarks
Turns each transform in a schema into one timed variant of a
Benchmark::cmpthesescript. -
Workflow deployment
Copies the dashboard and mutation GitHub Actions workflows into any CPAN module's
.github/workflows/.
How the pieces fit
- You write a schema — in YAML, or extracted from the module by
extract-schemas. fuzz-harness-generatorrenders a.tfile that contains a corpus, a random generator, boundary cases and assertions.test-generator-mutatemutates the module and runs your suite once per mutant, writingmutation.json.test-generator-indexcombines that with Devel::Cover and LCSAJ runtime data intocover_html/index.html.- Surviving mutants feed back into new schemas under
t/conf/, which the next run picks up automatically.
t/conf/*.yml ──▶ fuzz-harness-generator ──▶ t/*.t ──▶ prove
│
mutation survivors ─────────────┴──▶ augmented schema ──▶ next run kills them
It does not only test the happy path
Every constrained parameter gets three deterministic cases: just inside
the bound, exactly on it, and just outside it — the last marked as
expected to die. memberof lists are enumerated including one
rejected value, booleans are probed with 0/1,
true/false and a string that should be refused,
and strings are also tried with NUL bytes, non-ASCII and empty values
unless the schema turns those probes off.
Because the assertions come from the schema rather than from the implementation, the tests stay valid when the code changes shape. That is the point: black-box tests that say what a routine promises, next to the white-box tests you already write.
Install it, then follow the
quick start to get from an empty
t/conf to a passing harness in four commands. The
schema reference is the page you will come back
to.