gen-cases — Materialize Test Cases from a Spec¶
gen-cases reads a .testspec.yaml file and inserts the described test case entries into the tests slice of an existing _test.go. The test scaffolding must already exist — run gen first.
Experimental
This command is experimental. See the Spec Reference for format details and limitations. Alternatively, install the AI agent skills and let an AI generate cases directly from source code — no spec file required.
Syntax¶
spec-file is the path to a .testspec.yaml file.
Examples¶
# Preview output without writing (dry-run)
go-testgen gen-cases --dry-run ./engine/engine_start.testspec.yaml
# Write cases to the resolved test file
go-testgen gen-cases ./engine/engine_start.testspec.yaml
# Override output path
go-testgen gen-cases -o ./engine/engine_test.go ./engine/engine_start.testspec.yaml
# Replace existing entries (re-sync from an updated spec)
go-testgen gen-cases --force ./engine/engine_start.testspec.yaml
# Omit ai-hint comments (plain stubs only)
go-testgen gen-cases --no-hints ./engine/engine_start.testspec.yaml
# Show what was generated vs skipped
go-testgen gen-cases --verbose ./engine/engine_start.testspec.yaml
Flags¶
| Flag | Default | Description |
|---|---|---|
--dry-run |
false | Print generated code to stdout without modifying any file. |
-o, --output |
auto | Override the output _test.go path. |
--force |
false | Replace existing entries instead of skipping them. |
--no-hints |
false | Omit // ai-hint: comments from output. |
-v, --verbose |
false | Print a summary of generated/skipped entries. |
The .testspec.yaml Format¶
A spec file describes what to test in domain terms — without writing code. Example:
version: "1"
package: ./engine
function: Engine.Start
context:
subject_init: |
c := &Config{OnError: registerError}
e := New(c)
check_types:
- id: engine_check
type_name: engineTestCheckFn
composer: checkEngine
table_fields:
- name: notifiers
type: "[]model.Notifier"
role: input
cases:
- name: "connect-error"
description: >
A notifier with ConnectError configured fails to connect.
Engine.Start() should report the error via OnError.
fields:
notifiers: |
[]model.Notifier{
&dummy.DummyNotifier{Config: &dummy.Config{
Name: "dummy-01", ConnectError: fmt.Errorf("connecting"),
}},
}
checks:
- hasErrors(true)
- name: "success"
description: A notifier without ConnectError connects successfully.
fields:
notifiers: |
[]model.Notifier{
&dummy.DummyNotifier{Config: &dummy.Config{Name: "dummy-01"}},
}
checks:
- hasErrors(false)
What Gets Generated¶
For each case in the spec, gen-cases inserts a struct literal into the tests slice:
{
name: "connect-error",
notifiers: []model.Notifier{
&dummy.DummyNotifier{Config: &dummy.Config{
Name: "dummy-01",
ConnectError: fmt.Errorf("connecting"),
}},
},
checks: checkEngine(
hasErrors(true),
),
},
For cases with a before hook, it generates a function stub:
{
name: "json-marshal-error",
before: func(n *WebhookNotifier) {
// ai-hint: field-injection
// Inject into n.jsonMarshal a function that returns
// fmt.Errorf("error from json.Marshal").
},
checks: model.CheckResult(
model.CheckResultError("error from json.Marshal"),
),
},
The // ai-hint: comments tell a generative AI exactly what to implement. Use --no-hints to omit them.
Target File Resolution¶
The _test.go to modify is resolved in this order:
--outputflag, if provided.test_filefield in the spec, if present.- Derived automatically: loads the package with
go/packages, converts the receiver name to snake_case, and appends_test.go.
Examples:
function |
Derived filename |
|---|---|
Engine.Start |
engine_test.go |
WebhookNotifier.Deliver |
webhook_notifier_test.go |
DummyNotifier.Exists |
dummy_notifier_test.go |
NewEngine |
engine_test.go |
If the file does not exist, gen-cases returns an error — run gen first to create the scaffold.
Idempotency¶
gen-cases never inserts duplicate entries. Before inserting each case, it checks whether a struct literal with that name key already exists in the tests slice.
- Without
--force: existing entries are skipped with a warning (--verboseshows which ones). - With
--force: existing entries are replaced with the regenerated version from the spec.
Running gen-cases twice on the same spec and file produces identical output.