You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When working with failing tests it is useful to inspect logs from both the test code and the application code. The testing.T type (among other types) exports a method, Log(...any), that one can use to log during tests. The log output is formatted in such a way that it is easy to see which test it belongs to.
The application typically uses the log or log/slog package to do its logging with outputs to os.Stderr. These logs are separate from the test logs, making it hard to correlate to specific tests.
2024/10/08 13:19:47 hello world
--- FAIL: TestLog (0.00s)
example1_test.go:13: test start
example1_test.go:15: test end
FAIL
FAIL command-line-arguments 0.001s
FAIL
Note how "hello world" is the first thing that is printed and it's outside the context of the test. The output can vary by environment, go playground typically outputs in desired order.
A solution to this problem is to share a common logger between test and application code. I implemented a wrapper around a .Log(...any) interface to highlight the desired effect. A more robust implementation should probably implement the slog.Handler interface.
package main
import (
"log/slog""os""testing"
)
funcTestILogStuff_LogThis(t*testing.T) {
tests:=map[string]struct {
newLoggerfunc(tb testing.TB) *slog.Logger
}{
"text handler": {
newLogger: func(tb testing.TB) *slog.Logger { returnslog.New(slog.NewTextHandler(os.Stderr, nil)) },
},
"text handler with testing.TB writer": {
newLogger: func(tb testing.TB) *slog.Logger { returnslog.New(slog.NewTextHandler(testingTBWriter{tb: tb}, nil)) },
},
}
forname, tt:=rangetests {
t.Run(name, func(t*testing.T) {
logger:=tt.newLogger(t).
With("test name", t.Name())
unit:=Example{
Logger: logger.With("source", "the unit being tested"),
}
t.Log("test start")
unit.Log("hello world!")
t.Log("test end")
t.Fail()
})
}
}
typeExamplestruct {
Logger*slog.Logger
}
func (l*Example) Log(msgstring) {
l.Logger.Info(msg)
}
// testingTBWriter is a naive io.Writer implementation that writes to tb.Log.// This implementation is intended to demonstrate log output in tests.// A potential issue with this approach is that lines might not align with the// bytes given to the Write function.// A better approach would be to use a type that implements the slog.Handler// interface and wraps a TestLogger.typetestingTBWriterstruct {
tbTestLogger
}
func (wtestingTBWriter) Write(b []byte) (int, error) {
w.tb.Log(string(b))
returnlen(b), nil
}
typeTestLoggerinterface {
Log(...any)
}
Output (varies by environment):
time=2024-10-08T13:26:24.945+02:00 level=INFO msg="hello world!" "test name"=TestILogStuff_LogThis/text_handler source="the unit being tested"
--- FAIL: TestILogStuff_LogThis (0.00s)
--- FAIL: TestILogStuff_LogThis/text_handler (0.00s)
example_test.go:29: test start
example_test.go:31: test end
--- FAIL: TestILogStuff_LogThis/text_handler_with_testing.TB_writer (0.00s)
example_test.go:29: test start
example_test.go:57: time=2024-10-08T13:26:24.945+02:00 level=INFO msg="hello world!" "test name"=TestILogStuff_LogThis/text_handler_with_testing.TB_writer source="the unit being tested"
example_test.go:31: test end
FAIL
FAIL command-line-arguments 0.001s
FAIL
The text was updated successfully, but these errors were encountered:
Proposal Details
When working with failing tests it is useful to inspect logs from both the test code and the application code. The
testing.T
type (among other types) exports a method,Log(...any)
, that one can use to log during tests. The log output is formatted in such a way that it is easy to see which test it belongs to.The application typically uses the
log
orlog/slog
package to do its logging with outputs toos.Stderr
. These logs are separate from the test logs, making it hard to correlate to specific tests.This program highlights the issue:
Test output (varies by environment):
Note how "hello world" is the first thing that is printed and it's outside the context of the test. The output can vary by environment, go playground typically outputs in desired order.
A solution to this problem is to share a common logger between test and application code. I implemented a wrapper around a
.Log(...any)
interface to highlight the desired effect. A more robust implementation should probably implement theslog.Handler
interface.Play
Code
Output (varies by environment):
The text was updated successfully, but these errors were encountered: