May 9, 2020

21 tips to write better go tests

I have found the following tips to be generally useful in writing and working with go unit tests. They are not in any particular order:

  1. Use “t.Helper()" method to reduce the amount of noise in error reports. By calling the method a function is marked as a test helper function.

  2. Write a table-driven unit test. In most cases, this is a logical approach. Each table entry has a set of input and expected result(s). Naming each entry will save you time in quickly finding failed tests. Even if you have just one case to start with, creating a table encourages others and yourself to add more cases as they are discovered in the future.

  3. If you like to execute just one entry from a test table, pass the function name and table entry name to -run
    (example: go test -v -count=1 -run="TestMethodName/TableEntryName”)

  4. Do parallelize your tests when possible by calling “t.Parallel()” method. It reduces the time spent running tests. It could also expose potential race conditions when none are expected.

  5. When you need to turn off parallel execution, use -p 1 (example: go test -p 1 ./...). Any other tests from other packages run in parallel anyway. If you need all of your tests across packages to run one at a time, use -p 1.

  6. Use coverage visualization tools. They provide an excellent indication of what's not being covered by your tests. (example: go tool cover -html=cover.out)

  7. Write tests for private methods if you need to. Use bridge packages to expose internals to test packages (when you need to). These are usually named export_test.go in the standard library. Many opt to test private methods, and some don't:

    Some say yes: alt text

    And some say no: alt text

  8. Place your tests in a separate “_test” package (example: “math_test” for “math” package). This level of testing guarantees the implementer of the packages is met with fewer surprises.

  9. TDD rocks. Watch this

  10. Use text fixtures and golden files where needed. This is a great way to keep your tests easier to read. When the output of the files is changed, having a CLI “update” arg keeps the golden files up to date.
    var update = flag.Bool("update", false, "updates .golden files")

  11. Isolate resource consuming and slow tests with build tags. This gives you the freedom to run them when you need to. The test file only runs when the tags are provided in test run arguments.
    (example add: “// +build image”, and run with “go test -tags=image”)

  12. Use Skip and Short() to run a slimmer version of your tets.

  13. Run tests using the race detector and “-race” flag. The better coverage your code has the more beneficial the race detector is. You will be able to uncover design errors if there are any.

  14. There are many robust packages to aid with testing. Do some research before writing one. The httptest package, for example, is immensely useful in testing http clients.

  15. Know that you have the option to compile tests to go into a single binary and run tests if you need to. alt text

  16. Write your test setups to return a teardown method. Aim to teardown all setups.

  17. Use “init()” to check dependencies for unit tests. I generally prefer to have “init()” free code-base in Go. However, I understand the benefit of detecting your environment.

  18. Write clean and clear tests. Tests could double as a form of documentation and hints on how one can benefit from the codebase. For me writing bad/misleading/irrelevant tests are worse than having no tests at all.

  19. Place your test data, fixture, and golden files in “testdata”. The go tool will ignore directories named “testdata”. It also understands relative paths well, and you're free to place these files when you think is appropriate.

  20. Move away from abstracting and creating frameworks for your tests. Mock and helpful packages like testify are a great way to write clear and efficient tests.

  21. Fuzz. There are a few good fuzzing tools that can provide your application with random unexpected data input. Fuzzing is especially important for your applications that receive data from outside your network.

© Habtom Giorgis 2019