Problem: You want to set up data and an environment for testing and tear it down after the test is run.
Solution: You can create helper functions or use the TestMain feature to control the flow of the test functions.
Testing often needs data and an environment set up that is used in testing. These are called test fixtures . While test fixtures are usually destroyed outside of the scope of the test function, sometimes certain artifacts like files, database records, or configuration files need to be removed so they won’t interfere with subsequent testing.
There are a couple of ways to set up and tear down test fixtures.
The easiest way is to create helper functions. For example, you want to take an image file and flip it. In this example, you want to flip a PNG-format image of the Mona Lisa.
The algorithm is easy; first load the image file into a two-dimensional grid of pixels (where a pixel is represented by a color.Color struct).
The specific code you want to test is the flip function that takes the grid and flips the pixels in it:
func flip(grid [][]color.Color) { for x := 0; x < len(grid); x++ { col := grid[x] for y := 0; y < len(col)/2; y++ { k := len(col) - y - 1 col[y], col[k] = col[k], col[y] } } }
The obvious setup is to take a PNG file and load it into a grid to get it ready for testing.
After flipping the grid, you’ll save it to a file, then load it up again to check the dimensions. This leaves a file on the filesystem after the test is run, so you need to clean that up.
The straightforward way to do this is to call setup at the beginning of the test function and also call teardown before the end of the test function. Another way of doing this is to create a setup function that returns a teardown closure:
func setup(filename string) (teardown func(tempfile string), grid [][]color.Color) { grid = load(filename) teardown = func(tempfile string) { os.Remove(tempfile) } return }
You can then call the teardown using defer , which will ensure that the teardown happens at the end of the function:
func TestFlip(t *testing.T) { teardown, grid := setup("monalisa.png") defer teardown("flipped.png") flip(grid) save("flipped.png", grid) g := load("flipped.png") if len(g) != 321 || len(g[0]) != 480 { t.Error("Grid is wrong size", "width:", len(g), "length:", len(g[0])) } }
A test suite is a collection of test cases. Helper functions work well for smaller test suites, but in a larger one, it is tedious to call the helper function over and over again, especially if it takes up resources. Normally you would want to set up all the test fixtures up front and then tear it all down after the test suite completes.
The TestMain feature was added in Go 1.4, and it allows more flexible and lower-level control of setting up and tearing down test fixtures. If a test file contains the TestMain function, then Go will run TestMain instead of the tests directly. TestMain runs in the main goroutine and will run the rest of the test cases when you call m.Run . As a result, you can set up whatever you need before m.Run and tear down whatever fixtures were created after.
When you call m.Run , it will return an exit code that you can pass to os.Exit to exit the test suite cleanly:
func TestMain(m *testing.M) { fmt.Println("setup") exitCode := m.Run() fmt.Println("teardown") os.Exit(exitCode) }
When you run go test in the command line, you will get this:
% go test -v
setup
=== RUN TestAdd
testing_test.go:23: Result is: 3
- - - PASS: TestAdd (0.00s)
=== RUN TestAddWithTables
- - - PASS: TestAddWithTables (0.00s)
=== RUN TestFlip
- - - PASS: TestFlip (0.07s)
PASS
teardown
ok github.com/sausheong/gocookbook/ch18_testing 0.527s
As you can see, the setup runs before any test functions are run, and the teardown runs after all the test functions complete.