go-specs

module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 16, 2026 License: MIT

README

go-specs

Go Reference Go Report Card License

Project status

go-specs is pre-1.0 (v0.x). The public API (Describe, It, Context, Expectation, Mutator, etc.) is still settling and may change without notice between releases. Pin an exact version and check CHANGELOG.md before upgrading.

Description

go-specs is a fast, deterministic BDD-style testing framework for Go. It provides an expressive DSL for writing readable tests while staying close to the standard library and avoiding reflection and allocation overhead. By default, suites run sequentially in declaration order with no hidden concurrency, so test results are stable and reproducible; opt-in parallel execution (ItParallel, RunParallel, RunParallelBatched) is available where throughput matters.

Key features

  • BDD-style API — Describe, When, It, BeforeEach, and AfterEach for structured specs
  • Deterministic execution — Specs run in declaration order; no map iteration or nondeterministic scheduling
  • Low overhead — Zero allocations on the assertion fast path; compiled execution plan
  • Rich assertions — Expect(x).ToEqual(y), matchers (BeTrue, Equal, BeNil, etc.), and snapshot testing
  • Combinatorial testing — Path builder for deterministic exploration of parameter spaces
  • Lightweight mocking — Spies and argument matchers without heavy code generation

Installation

go get github.com/getsyntegrity/go-specs

Basic example

package math_test

import (
	"testing"

	"github.com/getsyntegrity/go-specs/specs"
)

func TestMath(t *testing.T) {
	specs.Describe(t, "math", func(s *specs.Spec) {
		s.It("adds numbers", func(ctx *specs.Context) {
			ctx.Expect(1 + 1).ToEqual(2)
		})
	})
}

Hooks example

Use BeforeEach and AfterEach for per-spec setup and teardown:

func TestMathWithHooks(t *testing.T) {
	specs.Describe(t, "math", func(s *specs.Spec) {
		s.BeforeEach(func(ctx *specs.Context) {
			// runs before each It
		})

		s.It("adds numbers", func(ctx *specs.Context) {
			ctx.Expect(add(1, 2)).ToEqual(3)
		})
	})
}

See examples/basic and examples/hooks for runnable examples.

Benchmarks

go-specs is built for low latency and zero allocations on the hot path. The tables below compare single-assertion cost, runner execution (1000 specs), hooks, matchers, and suite scaling against Testify and Gomega. Values are averaged from make bench-report (10 runs).

Assertions
Framework ns/op allocs
go-specs EqualTo ~1 ns 0
go-specs Expect().ToEqual ~7.4 ns 0
Testify Equal ~86 ns 0
Gomega Expect().To(Equal) ~241 ns 3
Runner (1000 specs, one assertion per spec)
Framework ns/op
go-specs ~1.7 µs
Testify ~85 µs
Gomega ~244 µs
Hooks (100 specs, 5 nested BeforeEach + one assertion per spec)
Framework ns/op
go-specs ~199 ns
Testify ~8.5 µs
Gomega ~24 µs
Matcher (Expect().To(Equal) style)
Framework ns/op allocs
go-specs ~7.8 ns 0
Gomega ~227 ns 3
Suite scaling (go-specs)
Specs Time
100 ~201 ns
1000 ~1.7 µs
10000 ~17 µs
50000 ~85 µs
Why go-specs is fast
  • Zero allocations — The assertion and runner hot paths allocate nothing on success (0 allocs/op above), reducing GC pressure.
  • Compiled execution plan — Suites are compiled once into a fixed program; the runner executes steps via direct function dispatch instead of per-spec lookups or reflection.
  • No reflection — Assertions use generics and direct comparison; the fast path avoids reflect.DeepEqual and runtime type switches.
  • Sequential runner loop — The default runner invokes spec and hook functions in a simple loop with direct calls; no matcher heap allocations or indirection on the hot path. Opt-in parallel paths (ItParallel, RunParallel, RunParallelBatched) trade this loop for a worker pool when a suite benefits from concurrency.

Reproducible benchmark suite: benchmarks/. From the repository root run make bench (quick) or make bench-report to generate a report in benchmarks/results/current.txt.

Architecture overview

go-specs compiles a spec tree (from Describe / It / BeforeEach / etc.) into an execution plan once. The runner then executes that plan in order: for each spec it runs before hooks, the spec body, and after hooks (LIFO). No maps or reflection are used at run time; the plan is a flat sequence of steps with direct function pointers. Parallel specs (ItParallel via the Builder) are grouped into a single step and run concurrently, then execution continues sequentially. MinimalRunner.RunParallel/RunParallelBatched offer an additional opt-in worker-pool execution path, distributing specs across goroutines instead of the default sequential loop. The repository is a single Go module; packages include:

  • specs — Core DSL, runner, context, and execution plan
  • assert — Matcher implementations (Equal, BeTrue, BeNil, etc.)
  • benchmarks — Benchmark suite (go-specs vs Testify vs Gomega)
  • mock — Spies and argument matchers
  • snapshots — Snapshot testing support
  • examples — Example tests (basic, hooks, parallel, and more)

Running benchmarks

From the repository root:

Target Description
make bench Run benchmarks once (output to terminal).
make bench-report Run benchmarks 10 times and write report to benchmarks/results/current.txt.
make bench-compare Compare benchmarks/results/previous.txt vs current.txt with benchstat (install: go install golang.org/x/perf/cmd/benchstat@latest).

Generate a report for the README or CI:

make bench-report

Compare before/after a change:

make bench-report
cp benchmarks/results/current.txt benchmarks/results/previous.txt
# ... make your changes ...
make bench-report
make bench-compare

Run by category (direct go test):

go test ./benchmarks -bench=BenchmarkAssertion -benchmem
go test ./benchmarks -bench=BenchmarkRunner -benchmem
go test ./benchmarks -bench=BenchmarkSuite_ -benchmem

See benchmarks/README.md for full benchmark layout and options.

Contributing

Contributions are welcome. Before submitting changes:

  1. Run the test suite: go test ./... and go test -race ./...
  2. Run the linter: go vet ./...
  3. If you change performance-sensitive code, run: go test ./benchmarks -bench=. -benchmem

Please open an issue or discussion for larger changes so we can align on direction.

License

MIT License. See LICENSE for details.

Directories

Path Synopsis
examples
spy
gen
generators
Package generators provides value generators for adversarial and property-style testing.
Package generators provides value generators for adversarial and property-style testing.
arena.go defines the index-based tree used by the registry for Analyze mode.
arena.go defines the index-based tree used by the registry for Analyze mode.
tools
perfcheck command
perfcheck compares benchmark results (go test -bench -benchmem -json) against a baseline and exits with non-zero status if any benchmark regresses by more than the threshold (default 10%).
perfcheck compares benchmark results (go test -bench -benchmem -json) against a baseline and exits with non-zero status if any benchmark regresses by more than the threshold (default 10%).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL