Proposal Details
When a test fails inside a chain of t.Helper() functions, the testing package reports only the topmost non-helper frame. This discards useful intermediate context about which code path led to the failure.
For example, if the call stack is A -> B -> C { t.Helper() } -> D { t.Helper() t.Fail() }, we only see the B call in the output, but not A.
Common use-case is that the entire call stack uses t.Helper(). However, there are cases when it is desired do a more fine-grained hierarchy of Helpers (see example below).
Frameworks such as stretchr/testify sidestep this problem by printing the entire stack in the error message. However, there is also demand for a finer-grained stack printing behaviour that integrates well with the existing t.Helper() infrastructure and does not duplicate what testing package provides: stretchr/testify#1702.
Proposal: print the entire stack (excluding t.Helper frames), instead of the topmost frame.
Benefits:
- Friendliness to third-party testing frameworks.
- Better observability into test failures by default.
- More user-friendly API: "forgetting" a
t.Helper does not render in a test failure output that is hard to interpret. By default, a "forgotten" t.Helper doesn't prevent printing all interesting call sites. In fact, the mandatory t.Helper boilerplate becomes optional if the user does not want to skip any frames from the output.
Example:
Code
package main
import "testing"
type Obj struct {
a int
b int
}
func check(t *testing.T, x int) {
t.Helper()
if x < 0 {
t.Fatal("negative value")
}
}
func (o *Obj) setupA(t *testing.T) {
t.Helper()
check(t, o.a)
}
func (o *Obj) setupB(t *testing.T) {
t.Helper()
check(t, o.b)
}
func setup(t *testing.T, x int) Obj {
o := Obj{a: x, b: x - 10}
o.setupA(t)
o.setupB(t) // t.Fail output points here
return o
}
func TestHelperDemo(t *testing.T) {
// We don't know which of these two calls fails.
// Alternatively, if we put t.Helper() inside setup(), we
// don't know which of setupA/setupB sub-steps fails.
_ = setup(t, 100)
_ = setup(t, 0)
}
go test output:
--- FAIL: TestHelperDemo (0.00s)
main_test.go:30: negative value
FAIL
exit status 1
FAIL example.com/testdemo 0.438s
Want something like:
--- FAIL: TestHelperDemo (0.00s)
main_test.go:30: negative value
main_test.go:36
FAIL
exit status 1
FAIL example.com/testdemo 0.438s
This preserves the current behaviour (the first line is still the non-helper call site) while adding the intermediate context needed to diagnose the failure. Implementation-wise, this seems like a minor refactoring around the callSite / frameSkip funcs.
Proposal Details
When a test fails inside a chain of
t.Helper()functions, thetestingpackage reports only the topmost non-helper frame. This discards useful intermediate context about which code path led to the failure.For example, if the call stack is
A -> B -> C { t.Helper() } -> D { t.Helper() t.Fail() }, we only see theBcall in the output, but notA.Common use-case is that the entire call stack uses
t.Helper(). However, there are cases when it is desired do a more fine-grained hierarchy ofHelpers (see example below).Frameworks such as
stretchr/testifysidestep this problem by printing the entire stack in the error message. However, there is also demand for a finer-grained stack printing behaviour that integrates well with the existingt.Helper()infrastructure and does not duplicate whattestingpackage provides: stretchr/testify#1702.Proposal: print the entire stack (excluding
t.Helperframes), instead of the topmost frame.Benefits:
t.Helperdoes not render in a test failure output that is hard to interpret. By default, a "forgotten"t.Helperdoesn't prevent printing all interesting call sites. In fact, the mandatoryt.Helperboilerplate becomes optional if the user does not want to skip any frames from the output.Example:
Code
go testoutput:Want something like:
This preserves the current behaviour (the first line is still the non-helper call site) while adding the intermediate context needed to diagnose the failure. Implementation-wise, this seems like a minor refactoring around the callSite /
frameSkipfuncs.