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.

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

How the pieces fit

  1. You write a schema — in YAML, or extracted from the module by extract-schemas.
  2. fuzz-harness-generator renders a .t file that contains a corpus, a random generator, boundary cases and assertions.
  3. test-generator-mutate mutates the module and runs your suite once per mutant, writing mutation.json.
  4. test-generator-index combines that with Devel::Cover and LCSAJ runtime data into cover_html/index.html.
  5. 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.

Start here

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.

Install →