An approach to testing
TL;DR
Software testing comes in two major styles: assertion-style (xUnit tradition: JUnit, pytest, NUnit, testify) and BDD-style (RSpec, Cucumber, Ginkgo).
Both are valid approaches. However, we think that testify’s assertion-style naturally aligns with Go’s core values: simplicity, explicitness, standard library first, minimal abstraction.
Testify brings powerful testing to Go developers who embrace these values:
zero-dependencies, reflection-based or generic assertions, and no framework (just works with go test).
If you chose Go for its philosophy, assertion-style testing is the natural extension of those values to your test suite.
Make testing better. Keep it Go
go-openapi/testify follows a simple philosophy: make Go testing better without reinventing it.
Testify follows the assertion style: it is not a BDD framework. So you won’t find chaining methods that produce English-like sentences.
Unlike frameworks that introduce new paradigms and require specialized tooling,
testify builds directly on top of Go’s great standard testing package.
It provides powerful assertions and utilities while preserving the familiar patterns that Go developers already know. Testing patterns and constructs remain standard.
Core Principles
1. Zero Dependencies
Testify has no external dependencies. Everything you need is self-contained, with internalized implementations of required functionality. This means:
- No dependency conflicts in your project
- No supply chain security concerns
- No version compatibility issues
- Chrome is opt-in (all extra features that need additional dependencies are opt-in)
2. Standard Go Compatibility
Works seamlessly with go test and the standard library:
- No special CLI tools required
- No framework-specific test runners
- Standard Go subtests with
t.Run() - Native IDE support out of the box
- Works with any Go test runner
3. Type Safety with Generics
Testify embraces Go’s type system:
- Most assertions come with a generic variant for compile-time type safety
- Catch type mismatches before tests even run
- On average 10x faster than reflection-based assertions
- Full type inference: no manual type parameters needed
- Complex cases that require dynamic typing use go reflection
4. Simplicity and Clarity
Keep testing straightforward:
- Function-based assertions with clear semantics
- No new DSL to learn
- Minimal cognitive overhead
- Immediate productivity for any Go developer
Testing Styles: Assertion vs. BDD
Software testing has evolved into two primary styles, each with passionate advocates across programming communities.
Assertion-Style Testing (xUnit tradition)
Core idea: Write tests as regular code with explicit assertions.
Originating with Kent Beck’s SUnit (Smalltalk) and popularized by JUnit (Java), this style emphasizes:
- Tests are functions/methods in the language
- Direct assertion calls verify behavior
- Standard language constructs for organization
- Minimal framework abstraction
Examples across languages:
Frameworks: JUnit, NUnit, xUnit.net, pytest, PHPUnit, Go’s testing package… and testify.
BDD-Style Testing (Behavior-Driven Development)
Core idea: Write tests as executable specifications in narrative form.
Originating with RSpec (Ruby) and influenced by Dan North’s BDD methodology, this style emphasizes:
- Tests describe behavior in natural language structure
- Hierarchical organization (describe/context/it)
- Focus on readability and documentation value
- Framework-specific DSL
Examples across languages:
Frameworks: RSpec, Jasmine, Mocha, Cucumber, behave, Ginkgo/Gomega
Both Are Valid
Assertion-style strengths:
- Low cognitive overhead (just code)
- Minimal framework abstraction
- IDE tooling works naturally
- Easy to learn and adopt
BDD-style strengths:
- Readable test specifications
- Natural hierarchical organization
- Self-documenting intent
- Stakeholder-friendly output
The debate continues across all programming communities. Neither style is objectively superior; they optimize for different values and team preferences.
My two-cents: I prefer assertion-style for unit and integration tests handled by development teams, where code clarity prevails.
I resort to BDD-style for tests driven by QA or UAT teams, where using natural language helps.
Assertion-Style and Go Values
While both styles have merit in general, assertion-style testing aligns naturally with Go’s core philosophy.
Go’s Design Values
Go emphasizes:
- Simplicity: maximize clarity
- Explicitness: No magic, no hidden behavior
- Standard library first: Build on solid foundations
- Readability: Code is read more than written
- Minimal abstraction: Minimize concepts
How Assertion-Style Matches Go
1. Simplicity
Assertion-style keeps tests simple: they’re just Go functions.
No new mental model. No framework semantics to learn. If you know Go, you know how to test with our lib.
2. Explicitness
Every assertion is an explicit function call with clear semantics:
Compare to matcher-based approaches where behavior is composed through framework objects. Assertion-style makes test intent immediately clear to a programmer.
3. Standard Library First
Testify builds on testing.T: no replacement, just enhancement.
Works with go test. Works with standard tooling. Works with the Go ecosystem.
4. Readability Through Directness
Go prioritizes code that’s easy to read and understand:
Standard control flow. Standard Go idioms. No DSL to decode. Better for most developers, perhaps less so for stakeholders not familiar with Go.
5. Minimal Abstraction
Go avoids abstraction for abstraction’s sake. Testify provides assertions: nothing more.
- No test lifecycle framework
- No dependency injection system
- No specialized runners
- No mandatory patterns
Just functions that verify behavior and produce clear errors. Solve the testing problem, don’t create a testing ecosystem.
The Natural Fit
If you appreciate Go’s philosophy and if you choose Go because you value simplicity, explicitness, and building on standards, then assertion-style testing is the natural extension of those values to your test suite.
BDD frameworks serve teams with different priorities (narrative specifications, framework-managed workflows, stakeholder communication). Those are valid priorities. But they optimize for values orthogonal to Go’s design philosophy. For such teams, Ginkgo/Gomega provides a great BDD testing framework.
For Go developers who embrace Go values, assertion-style testing is the idiomatic approach.. And testify is the tool.
Assertion-Style in Go: Testify vs. BDD Frameworks
Go’s testing ecosystem reflects the broader assertion-vs-BDD divide:
Different Philosophies
| Aspect | Testify (Assertion-Style) | Ginkgo/Gomega (BDD-Style) |
|---|---|---|
| Testing style | xUnit tradition | BDD tradition |
| Approach | Enhance standard testing | Replace with BDD framework |
| Integration | Works with go test directly | Requires ginkgo CLI tool |
| Learning curve | Immediate (standard Go) | Moderate (new DSL) |
| Dependencies | Zero external packages | Multiple framework packages |
| Type safety | Generic assertions | Reflection-based matchers |
| Organization | Standard Go subtests | Narrative hierarchy (Describe/Context/It) |
| Go philosophy | Aligns with Go values | Different priorities |
Example Comparison
Assertion-style (Testify):
BDD-style (Ginkgo/Gomega):
Both approaches are valid. They reflect different testing philosophies that span the entire software industry. The question for Go developers is: which style aligns with the values that drew you to Go in the first place?
Beyond style, approaches
Style is after all not that important. Perhaps more interesting is to consider alternative approaches to construct tests and test harnesses. Here are a few fascinating approaches
property-based testing
In this project, we’ve successfully experimented a property-based testing approach to validate more systematically
a few packages (see internal/testintegration).
This is using the excellent library rapid to produce random structures
submitted to spew.Dump and spotted quite a few actual bugs in there.
fuzzing
Also a randomized approach to testing. The fuzz driver that comes with the standard toolchain is smart: it selects the random candidates and track their code coverage path, so it biases its sampling toward exploring more code paths.
Fuzzing may be nicely coupled with property-based testing. Again, feel free to take a peek at our integration tests.
If you are interested, you may look at how fuzz tests are implemented in this project. We’ve also equipped our CI pipeline with a caching of the fuzz corpus and retrieval of captured failures.
mutesting
The “mutation testing” approach is more about assessing the quality of your tests, complementary to test coverage, rather than about testing functionality.
The principle is to inject bugs randomly in your code and verify that your test suite actually catch them: good tests are tests that catch bugs, not tests that walk 100% of the code and do not verify anything.
Unfortunately, support is still experimental for golang, although many languages already come with decent support for
this technique.
If you’re interested, https://github.com/zimmski/go-mutesting provides an old but convincing experience. This fork would love to continue the work https://github.com/fredbi/go-messmaker, but we are currently a bit short of time to implement the ideas behind it.
See also
- API Reference - Browse all 140 assertions by domain
- Generics Guide - Leverage type-safe assertions
- Migration Guide - Switch from stretchr/testify
- Examples - See testify in action