Documentation
¶
Overview ¶
Package be is a minimalist test assertion helper library.
Philosophy ¶
Tests usually should not fail. When they do fail, the failure should be repeatable. Therefore, it doesn't make sense to spend a lot of time writing good test messages. (This is unlike error messages, which should happen fairly often, and in production, irrepeatably.) Package be is designed to simply fail a test quickly and quietly if a condition is not met with a reference to the line number of the failing test. If the reason for having the test is not immediately clear from context, you can write a comment, like normal code. If you do need more extensive reporting to figure out why a test is failing, use be.DebugLog or be.Debug to capture more information.
Most tests just need simple equality testing, which is handled by be.Equal (for comparable types), be.AllEqual (for slices of comparable types), and be.DeepEqual (which relies on reflect.DeepEqual). Another common test is that a string or byte slice should contain or not some substring, which is handled by be.In and be.NotIn. Rather than package be providing every possible test helper, you are encouraged to write your own advanced helpers for use with be.True, while package be takes away the drudgery of writing yet another simple func nilErr(t *testing.T, err) { ... }.
The github.com/carlmjohnson/be/testfile subpackage has functions that make it easy to write file-based tests that ensure that the output of some transformation matches a golden file. Subtests can automatically be run for all files matching a glob pattern, such as testfile.Run(t, "testdata/*/input.txt", ...). If the test fails, the failure output will be written to a file, such as "testdata/basic-test/-failed-output.txt", and then the output can be examined via diff testing with standard tools. Set the environmental variable TESTFILE_UPDATE to update the golden file.
Every test in the be package requires a testing.TB as its first argument. There are various clever ways to get the testing.TB implicitly,* but package be is designed to be simple and explicit, so it's easiest to just always pass in a testing.TB the boring way.
Example ¶
// mock *testing.T for example purposes
t := be.Relaxed(&mockingT{})
be.Equal(t, "hello", "world") // bad
be.Equal(t, "goodbye", "goodbye") // good
be.Unequal(t, "hello", "world") // good
be.Unequal(t, "goodbye", "goodbye") // bad
s := []int{1, 2, 3}
be.AllEqual(t, []int{1, 2, 3}, s) // good
be.AllEqual(t, []int{3, 2, 1}, s) // bad
var err error
be.Zero(t, err) // good
be.ErrorIs(t, nil, err) // good
be.Nonzero(t, err) // bad
be.ErrorIs(t, os.ErrPermission, err) // bad
err = errors.New("(O_o)")
var asErr *os.PathError
be.ErrorAs(t, &asErr, err) // bad
be.Nonzero(t, err) // good
type mytype string
var mystring mytype = "hello, world"
be.Match(t, `world`, mystring) // good
be.Match(t, `World`, mystring) // bad
be.Match(t, `^\W*$`, []byte("\a\b\x00\r\t")) // good
be.Match(t, `^\W*$`, []byte("\a\bo\r\t")) // bad
seq := strings.FieldsSeq("1 2 3 4")
be.EqualLength(t, 4, seq) // good
be.EqualLength(t, 1, seq) // bad
be.AtLeastLength(t, 1, seq) // good
be.AtLeastLength(t, 5, seq) // bad
be.AtLeastLength(t, 3, "123") // good
be.AtLeastLength(t, 4, "123") // bad
Output: want: hello; got: world got: goodbye want: [3 2 1]; got: [1 2 3] got: <nil> got errors.Is(<nil>, permission denied) == false got errors.As((O_o), **fs.PathError) == false /World/ !~ "hello, world" /^\W*$/ !~ "\a\bo\r\t" want len(seq) == 1; got at least 2 want len(seq) >= 5; got 4 want len(seq) >= 4; got 3
Index ¶
- func AllEqual[T comparable](t testing.TB, want, got []T)
- func AtLeastLength(t testing.TB, want int, seq any)
- func Debug(t testing.TB, f func())
- func DebugLog(t testing.TB, format string, args ...any)
- func DeepEqual[T any](t testing.TB, want, got T)
- func Equal[T comparable](t testing.TB, want, got T)
- func EqualLength(t testing.TB, want int, seq any)
- func ErrorAs[T error](t testing.TB, want *T, got error)
- func ErrorIs(t testing.TB, want, got error)
- func False(t testing.TB, value bool)
- func In[byteseq ~string | ~[]byte](t testing.TB, needle string, haystack byteseq)
- func Match[byteseq ~string | ~[]byte](t testing.TB, pattern string, got byteseq)
- func NilErr(t testing.TB, err error)
- func Nonzero[T any](t testing.TB, value T)
- func NotIn[byteseq ~string | ~[]byte](t testing.TB, needle string, haystack byteseq)
- func Panicked(fn func()) (r any)
- func Relaxed(t testing.TB) testing.TB
- func True(t testing.TB, value bool)
- func Unequal[T comparable](t testing.TB, bad, got T)
- func Zero[T any](t testing.TB, value T)
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AllEqual ¶ added in v0.22.2
func AllEqual[T comparable](t testing.TB, want, got []T)
AllEqual calls t.Fatalf if want != got.
func AtLeastLength ¶ added in v0.25.1
AtLeastLength calls t.Fatalf if seq has a length that is not at least want. The type of seq must be array, array pointer, slice, map, string, channel, iter.Seq, or iter.Seq2. For channels and iterators, the values are consumed to get the sequence length.
func Debug ¶ added in v0.22.4
Debug takes a callback that will only be run after the test fails.
Example ¶
// mock *testing.T for example purposes
t := &mockingT{}
// If a test fails, the callbacks will be replayed in LIFO order
t.Run("logging-example", func(*testing.T) {
x := 1
x1 := x
be.Debug(t, func() {
// record some debug information about x1
fmt.Println("x1:", x1)
})
x = 2
x2 := x
be.Debug(t, func() {
// record some debug information about x2
fmt.Println("x2:", x2)
})
be.Equal(t, x, 3)
})
// If a test succeeds, nothing will be replayed
t.Run("silent-example", func(*testing.T) {
y := 1
y1 := y
be.Debug(t, func() {
// record some debug information about y1
fmt.Println("y1:", y1)
})
y = 2
y2 := y
be.Debug(t, func() {
// record some debug information about y2
fmt.Println("y2:", y2)
})
be.Unequal(t, y, 3)
})
Output: want: 2; got: 3 x2: 2 x1: 1
func DebugLog ¶ added in v0.22.4
DebugLog records a message that will only be logged after the test fails.
Example ¶
// mock *testing.T for example purposes
t := &mockingT{}
// If a test fails, the logs will be replayed in LIFO order
t.Run("logging-example", func(*testing.T) {
x := 1
be.DebugLog(t, "x: %d", x)
x = 2
be.DebugLog(t, "x: %d", x)
be.Equal(t, x, 3)
})
// If a test succeeds, nothing will be replayed
t.Run("silent-example", func(*testing.T) {
y := 1
be.DebugLog(t, "y: %d", y)
y = 2
be.DebugLog(t, "y: %d", y)
be.Unequal(t, y, 3)
})
Output: want: 2; got: 3 x: 2 x: 1
func DeepEqual ¶ added in v0.22.4
DeepEqual calls t.Fatalf if want and got are different according to reflect.DeepEqual.
Example ¶
// mock *testing.T for example purposes
t := be.Relaxed(&mockingT{})
// good
m1 := map[int]bool{1: true, 2: false}
m2 := map[int]bool{1: true, 2: false}
be.DeepEqual(t, m1, m2)
// bad
var s1 []int
s2 := []int{}
be.DeepEqual(t, s1, s2) // DeepEqual is picky about nil vs. len 0
Output: reflect.DeepEqual([]int(nil), []int{}) == false
func Equal ¶ added in v0.22.2
func Equal[T comparable](t testing.TB, want, got T)
Equal calls t.Fatalf if want != got.
func EqualLength ¶ added in v0.25.1
EqualLength calls t.Fatalf if seq has a length that is not exactly want. The type of seq must be array, array pointer, slice, map, string, channel, iter.Seq, or iter.Seq2. For channels and iterators, the values are consumed to get the sequence length.
func ErrorAs ¶ added in v0.25.2
ErrorIs calls t.Fatalf if got cannot be assigned to want by errors.As.
func In ¶ added in v0.22.3
In calls t.Fatalf if needle is not contained in the string or []byte haystack.
func Match ¶ added in v0.25.2
Match calls t.Fatalf if got does not match the regexp pattern.
The pattern must compile.
func NotIn ¶ added in v0.22.4
NotIn calls t.Fatalf if needle is contained in the string or []byte haystack.
func Panicked ¶ added in v0.24.1
func Panicked(fn func()) (r any)
Panicked runs the callback and returns the recovered panic, if any.
Example ¶
// mock *testing.T for example purposes
t := &mockingT{}
divide := func(num, denom int) int {
return num / denom
}
// Test that division by zero panics
be.Nonzero(t, be.Panicked(func() {
divide(1, 0)
}))
// Because a panic fails a test by default,
// testing that an operation does not panic is less necessary,
// but may be helpful in a table test.
for _, testcase := range []struct {
num, denom, want int
shouldPanic bool
}{
{0, 1, 0, false},
{1, 1, 1, false},
{1, 0, 0xbadc0ffee, true},
{0, 0, 0xbadc0ffee, true},
} {
got := 0xbadc0ffee
panicVal := be.Panicked(func() {
got = divide(testcase.num, testcase.denom)
})
be.Equal(t, testcase.want, got)
be.Equal(t, testcase.shouldPanic, panicVal != nil)
}
func Relaxed ¶ added in v0.22.5
Relaxed returns a testing.TB which replaces calls to t.FailNow, t.Fatal, and t.Fatalf with calls to t.Fail, t.Error, and t.Errorf respectively.
Example ¶
// mock *testing.T for example purposes
t := &mockingT{}
t.Run("dies on first error", func(*testing.T) {
be.Equal(t, 1, 2)
be.Equal(t, 3, 4)
})
t.Run("shows multiple errors", func(*testing.T) {
relaxedT := be.Relaxed(t)
be.Equal(relaxedT, 5, 6)
be.Equal(relaxedT, 7, 8)
})
Output: want: 1; got: 2 want: 5; got: 6 want: 7; got: 8
func Unequal ¶ added in v0.22.2
func Unequal[T comparable](t testing.TB, bad, got T)
Unequal calls t.Fatalf if got == bad.
Types ¶
This section is empty.