be

package module
v0.23.2 Latest Latest
Warning

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

Go to latest
Published: Oct 19, 2023 License: MIT Imports: 5 Imported by: 0

README

Be Go Reference Go Report Card Coverage Status Mentioned in Awesome Go

Package be is the minimalist testing helper for Go.

Inspired by Mat Ryer and Alex Edwards.

Features

  • Simple and readable test assertions using generics
  • Built-in helpers for common cases like be.NilErr and be.In
  • Fail fast by default but easily switch to relaxed with be.Relaxed(t)
  • Helpers for testing against golden files with the testfile subpackage
  • No dependencies: just uses standard library

Example usage

Test for simple equality using generics:

// Test two unequal strings
be.Equal(t, "hello", "world")     // bad
// t.Fatal("want: hello; got: world")
// Test two equal strings
be.Equal(t, "goodbye", "goodbye") // good
// Test equal integers, etc.
be.Equal(t, 200, resp.StatusCode)
be.Equal(t, tc.wantPtr, gotPtr)

// Test for inequality
be.Unequal(t, "hello", "world")     // good
be.Unequal(t, "goodbye", "goodbye") // bad
// t.Fatal("got: goodbye")

Test for equality of slices:

s := []int{1, 2, 3}
be.AllEqual(t, []int{1, 2, 3}, s) // good
be.AllEqual(t, []int{3, 2, 1}, s) // bad
// t.Fatal("want: [3 2 1]; got: [1 2 3]")

Handle errors:

var err error
be.NilErr(t, err)   // good
be.Nonzero(t, err) // bad
// t.Fatal("got: <nil>")
err = errors.New("(O_o)")
be.NilErr(t, err)   // bad
// t.Fatal("got: (O_o)")
be.Nonzero(t, err) // good

Check substring containment:

be.In(t, "world", "hello, world") // good
be.In(t, "World", "hello, world") // bad
// t.Fatal("World" not in "hello, world")
be.NotIn(t, "\x01", []byte("\a\b\x00\r\t")) // good
be.NotIn(t, "\x00", []byte("\a\b\x00\r\t")) // bad
// t.Fatal("\x00" in "\a\b\x00\r\t")

Test anything else:

be.True(t, o.IsValid())
be.True(t, len(pages) >= 20)

Test using goldenfiles:

// Start a sub-test for each .txt file
testfile.Run(t, "testdata/*.txt", func(t *testing.T, path string) {
	// Read the file
	input := testfile.Read(t, path)

	// Do some conversion on it
	type myStruct struct{ Whatever string }
	got := myStruct{strings.ToUpper(input)}

	// See if the struct is equivalent to a .json file
	wantFile := strings.TrimSuffix(path, ".txt") + ".json"
	testfile.EqualJSON(t, wantFile, got)

	// If it's not equivalent,
	// the got struct will be dumped
	// to a file named testdata/-failed-test-name.json
})

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, just like in 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 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 tool in the be module 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.

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.NilErr(t, err)  // good
be.Nonzero(t, err) // bad
err = errors.New("(O_o)")
be.NilErr(t, err)  // bad
be.Nonzero(t, err) // good

type mytype string
var mystring mytype = "hello, world"
be.In(t, "world", mystring)                 // good
be.In(t, "World", mystring)                 // bad
be.NotIn(t, "\x01", []byte("\a\b\x00\r\t")) // good
be.NotIn(t, "\x00", []byte("\a\b\x00\r\t")) // bad
Output:

want: hello; got: world
got: goodbye
want: [3 2 1]; got: [1 2 3]
got: <nil>
got: (O_o)
"World" not in "hello, world"
"\x00" in "\a\b\x00\r\t"

Index

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 Debug added in v0.22.4

func Debug(t testing.TB, f func())

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

func DebugLog(t testing.TB, format string, args ...any)

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

func DeepEqual[T any](t testing.TB, want, got T)

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 False added in v0.22.3

func False(t testing.TB, value bool)

False calls t.Fatalf if value is not false.

func In added in v0.22.3

func In[byteseq ~string | ~[]byte](t testing.TB, needle string, haystack byteseq)

In calls t.Fatalf if needle is not contained in the string or []byte haystack.

func NilErr added in v0.22.3

func NilErr(t testing.TB, err error)

NilErr calls t.Fatalf if err is not nil.

func Nonzero added in v0.22.2

func Nonzero[T any](t testing.TB, value T)

Nonzero calls t.Fatalf if value == the zero value for T.

func NotIn added in v0.22.4

func NotIn[byteseq ~string | ~[]byte](t testing.TB, needle string, haystack byteseq)

NotIn calls t.Fatalf if needle is contained in the string or []byte haystack.

func Relaxed added in v0.22.5

func Relaxed(t testing.TB) testing.TB

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 True added in v0.22.3

func True(t testing.TB, value bool)

True calls t.Fatalf if value is not true.

func Unequal added in v0.22.2

func Unequal[T comparable](t testing.TB, bad, got T)

Unequal calls t.Fatalf if got == bad.

func Zero

func Zero[T any](t testing.TB, value T)

Zero calls t.Fatalf if value != the zero value for T.

Types

This section is empty.

Directories

Path Synopsis
Package testfile has test helpers that work by comparing files.
Package testfile has test helpers that work by comparing files.

Jump to

Keyboard shortcuts

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