gotests is a powerful Go test generator that automatically creates table-driven tests from your source code. It analyzes function and method signatures to generate comprehensive test scaffolding, saving you time and ensuring consistency across your test suite.
- 🚀 Zero-config test generation - Works out of the box with any Go project
- 🎯 Smart test scaffolding - Generates complete table-driven test structure with proper type handling
- 🔧 Flexible filtering - Generate tests for specific functions, exported functions, or entire packages
- 📦 Auto-imports - Automatically adds required imports to test files
- 🧬 Full generics support - Works seamlessly with Go 1.18+ type parameters
- 🔄 Recursive generation - Process entire directory trees with
./...pattern - 🎨 Custom templates - Built-in support for testify and custom test templates
- ⚡ Parallel subtests - Optional parallel test execution support
The following shows gotests in action using the official Sublime Text 3 plugin. Plugins also exist for Emacs, also Emacs, Vim, Atom Editor, Visual Studio Code, and IntelliJ Goland.
Minimum Go version: Go 1.22
Use go install to install and update:
$ go install github.com/cweill/gotests/gotests@latestFrom the commandline, gotests can generate Go tests for specific source files or an entire directory. By default, it prints its output to stdout.
$ gotests [options] PATH ...Available options:
-all generate tests for all functions and methods
-excl regexp. generate tests for functions and methods that don't
match. Takes precedence over -only, -exported, and -all
-exported generate tests for exported functions and methods. Takes
precedence over -only and -all
-i print test inputs in error messages
-named switch table tests from using slice to map (with test name for the key)
-only regexp. generate tests for functions and methods that match only.
Takes precedence over -all
-nosubtests disable subtest generation when >= Go 1.7
-parallel enable parallel subtest generation when >= Go 1.7.
-w write output to (test) files instead of stdout
-template_dir Path to a directory containing custom test code templates. Takes
precedence over -template. This can also be set via environment
variable GOTESTS_TEMPLATE_DIR
-template Specify custom test code templates, e.g. testify. This can also
be set via environment variable GOTESTS_TEMPLATE
-template_params_file read external parameters to template by json with file
-template_params read external parameters to template by json with stdin
-use_go_cmp use cmp.Equal (google/go-cmp) instead of reflect.DeepEqual
-ai generate test cases using AI (requires Ollama)
-ai-model AI model to use (default "qwen2.5-coder:0.5b")
-ai-endpoint Ollama API endpoint (default "http://localhost:11434")
-ai-min-cases minimum number of test cases to generate with AI (default 3)
-ai-max-cases maximum number of test cases to generate with AI (default 10)
-version print version information and exit
gotests can generate intelligent test cases using local LLMs via Ollama. This feature analyzes your function implementations and generates realistic test values, edge cases, and error conditions.
-
Install Ollama (https://ollama.ai)
-
Pull a model:
ollama pull qwen2.5-coder:0.5b # Small, fast model (400MB) # or ollama pull llama3.2:latest # Larger, more capable (2GB)
-
Generate tests with AI:
gotests -all -ai -w yourfile.go
Given this function:
func CalculateDiscount(price float64, percentage int) (float64, error) {
if price < 0 {
return 0, errors.New("price cannot be negative")
}
if percentage < 0 || percentage > 100 {
return 0, errors.New("percentage must be between 0 and 100")
}
discount := price * float64(percentage) / 100.0
return price - discount, nil
}The AI generates (showing 3 cases; by default, the AI generates between 3-10 cases):
func TestCalculateDiscount(t *testing.T) {
type args struct {
price float64
percentage int
}
tests := []struct {
name string
args args
want float64
wantErr bool
}{
{
name: "valid discount",
args: args{price: 100.0, percentage: 20},
want: 80.0,
wantErr: false,
},
{
name: "negative price",
args: args{price: -10.0, percentage: 20},
want: 0,
wantErr: true,
},
{
name: "invalid percentage",
args: args{price: 100.0, percentage: 150},
want: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := CalculateDiscount(tt.args.price, tt.args.percentage)
if (err != nil) != tt.wantErr {
t.Errorf("CalculateDiscount() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("CalculateDiscount() = %v, want %v", got, tt.want)
}
})
}
}# Use a different model
gotests -all -ai -ai-model llama3.2:latest -w yourfile.go
# Generate a specific number of test cases (min = max)
gotests -all -ai -ai-min-cases 5 -ai-max-cases 5 -w yourfile.go
# Generate a range of test cases (AI chooses between 3-7)
gotests -all -ai -ai-min-cases 3 -ai-max-cases 7 -w yourfile.go
# Combine with other flags
gotests -exported -ai -parallel -w yourfile.go- Analyzes function implementation and logic
- Generates realistic test values based on actual code
- Creates test cases for edge cases and error conditions
- Falls back to TODO comments if generation fails
- Works offline with local models (privacy-first)
✅ Simple types (int, string, bool, float) ✅ Complex types (slices, maps, structs, pointers) ✅ Error returns and validation ✅ Variadic parameters ✅ Methods with receivers ✅ Multiple return values
What data is sent to the LLM:
- Function signatures (name, parameters, return types)
- Complete function bodies including all code and comments
- No file paths or project context
Privacy considerations:
⚠️ Function bodies may contain sensitive information - business logic, algorithms, or credentials/secrets in comments- ✅ Local-first by default - Using Ollama keeps all data on your machine; nothing is sent to external servers
- ✅ Offline operation - AI generation works completely offline with local models
- 🔒 Recommendation: Avoid using
-aion code containing secrets, API keys, or proprietary algorithms in comments
If using cloud providers in the future:
- Function source code will be transmitted to the cloud provider's API
- Review the provider's data retention and privacy policies
- Consider using
-aionly on non-sensitive codebases
Given a file math.go:
package math
func Add(a, b int) int {
return a + b
}Generate a test:
$ gotests -only Add -w math.goThis creates math_test.go with:
func TestAdd(t *testing.T) {
type args struct {
a int
b int
}
tests := []struct {
name string
args args
want int
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Add(tt.args.a, tt.args.b); got != tt.want {
t.Errorf("Add() = %v, want %v", got, tt.want)
}
})
}
}$ gotests -all -exported -w .This generates tests for all exported functions in the current directory.
$ gotests -all -w ./...This generates tests for all functions in the current directory and all subdirectories.
$ gotests -all -template testify -w calculator.goThis generates tests using the testify assertion library.
# Generate tests for all exported functions in a package
$ gotests -exported -w pkg/*.go
# Generate only tests for specific functions matching a pattern
$ gotests -only "^Process" -w handler.go
# Generate tests excluding certain functions
$ gotests -all -excl "^helper" -w utils.go
# Generate parallel subtests
$ gotests -all -parallel -w service.gogotests fully supports Go generics (type parameters) introduced in Go 1.18+. It automatically generates tests for generic functions and methods on generic types.
Given this generic function:
func FindFirst[T comparable](slice []T, target T) (int, error) {
for i, v := range slice {
if v == target {
return i, nil
}
}
return -1, ErrNotFound
}Running gotests -all -w yourfile.go generates:
func TestFindFirst(t *testing.T) {
type args struct {
slice []string
target string
}
tests := []struct {
name string
args args
want int
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := FindFirst[string](tt.args.slice, tt.args.target)
if (err != nil) != tt.wantErr {
t.Errorf("FindFirst() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("FindFirst() = %v, want %v", got, tt.want)
}
})
}
}gotests also supports methods on generic types:
type Set[T comparable] struct {
m map[T]struct{}
}
func (s *Set[T]) Add(v T) {
if s.m == nil {
s.m = make(map[T]struct{})
}
s.m[v] = struct{}{}
}Generates:
func TestSet_Add(t *testing.T) {
type args struct {
v string
}
tests := []struct {
name string
s *Set[string]
args args
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &Set[string]{}
s.Add(tt.args.v)
})
}
}gotests uses intelligent defaults for type parameter instantiation:
any→intcomparable→string- Union types (
int64 | float64) → first option (int64) - Approximation constraints (
~int) → underlying type (int)
This ensures generated tests use appropriate concrete types for testing generic code.
Contributing guidelines are in CONTRIBUTING.md.
gotests is released under the Apache 2.0 License.