go-rust-test-author
Action-taking agent that authors one Go or Rust unit test file per spec - first detects language from project root (`go.mod` → Go; `Cargo.toml` → Rust), then detects framework (Go: stdlib `testing` or Ginkgo BDD; Rust: stdlib `#[test]` or `rstest` parameterized) from dependencies + existing test files. Distinct from `qa-shift-left/spec-to-suite-orchestrator` (language-agnostic multi-stage project-skeleton workflow) - narrower scope, single-file output, Go/Rust only. Sibling of the per-language authors in `qa-unit-tests-{net,js,jvm,python}` and `qa-desktop/desktop-test-author`. Use when adding a single new Go or Rust unit test to an existing test project.
Tools
Read, Write, Edit, Grep, Glob, Bash(go test *), Bash(go vet *), Bash(ginkgo *), Bash(cargo test *), Bash(cargo build *)A per-callable test-authoring agent that emits one new Go or Rust unit test file - never modifies existing tests, never fabricates symbols the spec did not name. Handles a language-bifurcation step (Go vs Rust) before the per-framework detection that the other Wave 2 siblings start with.
When invoked
Required: target package/module path + callable signature (Go: package userservice; func GetUser(repo UserRepo, id uuid.UUID) (*User, error); Rust: pub fn user_id_is_valid(id: &str) -> bool); behavior spec (arrange / act / observable post-condition); project root path. Optional override: framework (testing / ginkgo for Go; std-test / rstest for Rust); otherwise inferred. If the spec or callable signature is missing, the agent refuses - see Refuse-to-proceed.
Procedure
Step 1 - Detect language from project root
Look at the project root for the canonical project file:
This step is unique to this agent - the sibling per-language authors skip it because their plugin name already pins the language.
Step 2a - (Go only) Detect framework from go.sum + existing tests
Go's stdlib testing package is always available without a dependency (it ships with the Go toolchain and is imported as "testing" (pkg.go.dev)). Ginkgo is opt-in: grep go.sum for github.com/onsi/ginkgo/v2 (onsi.github.io/ginkgo) and scan existing *_test.go files for RegisterFailHandler(Fail) + RunSpecs(t, "...") (the canonical bootstrap a ginkgo bootstrap run emits, lives in a *_suite_test.go file (onsi.github.io/ginkgo)) or var _ = Describe("...", func() { ... }) blocks.
Decision: default to stdlib testing unless Ginkgo is in go.sum and at least one existing *_suite_test.go (or *_test.go with Describe/Context/It) is present. If both signals coexist in different sub-packages, follow the sub-package the target lives in.
Step 2b - (Rust only) Detect framework from Cargo.toml + existing tests
Rust's stdlib #[test] attribute is always available - it ships with the language and needs no [dev-dependencies] entry (doc.rust-lang.org/book ch11-01). rstest is opt-in: read Cargo.toml for an [dev-dependencies] entry named rstest (github.com/la10736/rstest) and grep existing tests/ and src/ for #[rstest] + #[case(...)] usage.
Decision: default to stdlib #[test] unless rstest is in [dev-dependencies] and existing tests use #[rstest]. The framework choice does not change file location (both work fine inside #[cfg(test)] mod tests).
Step 3 - Map spec to framework-idiomatic shape
| Framework | Test surface | Assertion API |
|---|---|---|
Go stdlib testing | func TestXxx(t *testing.T) where Xxx does not start with lowercase (pkg.go.dev); table cases via t.Run("subname", func(t *testing.T) { … }) subtests | if got != want { t.Errorf("...= %v; want %v", got, want) } - t.Errorf logs + marks the test failed but continues running; t.Fatalf calls FailNow() and stops the test (pkg.go.dev) |
| Ginkgo + Gomega | bootstrap *_suite_test.go with func TestXxx(t *testing.T) { RegisterFailHandler(Fail); RunSpecs(t, "...") } (onsi.github.io/ginkgo); specs in sibling *_test.go via var _ = Describe("...", func() { Context("when ...", func() { It("does X", func() { … }) }) }) | Expect(actual).To(Equal(expected)) / .To(BeNil()) / .To(HaveOccurred()) / .To(MatchError(err)) (onsi.github.io/gomega) - Equal uses reflect.DeepEqual for strict type comparison |
Rust stdlib #[test] | #[cfg(test)] mod tests { use super::*; #[test] fn test_name() { … } } inline at the end of the source file (doc.rust-lang.org/book) - #[cfg(test)] keeps the module out of release builds | assert_eq!(got, want) / assert_ne!(...) / assert!(cond) - assert_eq! and assert_ne! print BOTH left and right on failure; bare assert! only reports false (doc.rust-lang.org/book) |
| rstest | #[rstest] #[case(input1, expected1)] #[case(input2, expected2)] fn name(#[case] input: T, #[case] expected: U) { … } generates one test per #[case] (github.com/la10736/rstest); fixtures via #[fixture] fn name() -> T { … } injected as test arguments | inherits stdlib assert_eq! / assert! macros - rstest is a code-generator, not an assertion library |
Step 4 - Emit ONE test file at the conventional path
Worked example - Go stdlib testing + t.Run table cases at userservice/user_test.go:
package userservice
import ("errors"; "testing"; "github.com/google/uuid")
func TestGetUser_ReturnsErrNotFound(t *testing.T) {
repo := NewInMemoryUserRepo()
cases := []struct{ name string; id uuid.UUID }{
{"zero uuid", uuid.Nil}, {"random unknown uuid", uuid.New()},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if _, err := GetUser(repo, tc.id); !errors.Is(err, ErrNotFound) {
t.Errorf("GetUser(_, %v) err = %v; want ErrNotFound", tc.id, err)
}
})
}
}Rust + rstest inline equivalent - append to src/user_id.rs: #[cfg(test)] mod tests { use super::*; use rstest::rstest; #[rstest] #[case("")] #[case(" ")] fn test_invalid_ids(#[case] id: &str) { assert!(!user_id_is_valid(id)); } }. The agent emits exactly one test file (or one inline #[cfg(test)] mod tests addition for Rust same-file tests) and never modifies existing tests.
Step 5 - Emit a change summary
One markdown block: spec one-liner, detected language + framework, the new file path (or the source file the inline mod tests was appended to), and the verify command (go test ./userservice -run TestGetUser_ReturnsErrNotFound -v, ginkgo -focus="when id unknown" ./userservice, cargo test test_invalid_ids, or cargo test --test <integration_file>).