Testing in Golang
Testing your code is as important as writing it in the first place. Writing tests for the code is a good way to ensure quality and improve reliability. This article will highlight some of the Golang conventions which are followed while writing unit tests.
Go comes with a testing package which provides support for automated testing of Go packages. The command go test automates the execution of any test function which is found in “*_test.go” files corresponding to the package under test. For a given file, say foo.go, the test is placed in a file called foo_test.go in the same package or a separate test package. The test file will be excluded from regular package builds but will be included when the “go test” command is run.
Here’s a sample Go code illustrating a string reverse program
The test file reverse_test.go would be in the same package containing a test function in the format TestXxxx, where Xxx can be any alphanumeric string.
Different developers follow different convention when it comes to naming the test function. I usually give a name that describes the test case, in the format
Test<Function under test>ToReturn<Expected output><Conditions>
An instance of *testing.T is injected to the test function, and this is used to control the test flow and output. To run the test we use the go test command
go test -v
=== RUN TestReverseToReturnReversedInputString
— — PASS: TestReverseToReturnReversedInputString (0.00s)
The usual convention with the assert condition is to have the expected result first and then the actual result. So if the test fails, you get a message
reverse_test.go:10: Expected Hello but got olleH
By default, tests within a specific package are executed sequentially, but it is possible to mark some tests as safe for parallel execution using t.Parallel() within the test function. When there are multiple packages and there’s a need to run all the unit tests,
go test ./...
The test binaries of all packages, the one in the current directory and all subdirectories, are built and then run at the same time in parallel. The -p flag can be used to specify the number of builds and tests to run in parallel. Running
go test -p 1 ./...
constrains the tool to build and test one package at a time. If you want to run a particular test case, you can do so by using regex
go test -run TestNameRegexp
There are external packages like Testify which many other functionalities. The Assert and Require package of Testify provide some helpful methods that allow you to write better test code in Go.
Mocking in Golang
Since Go is strongly typed and statically compiled, mocking and stubbing is harder to achieve and far less flexible. Dependency injection is good coding practice in most languages, but in dynamic languages, you can get away with isolating dependencies with runtime magic and stubbing in your test code. In Go, you need to always inject your dependencies since you can’t patch things at runtime in a safe way.
Here’s a simple Go program illustrating injection of dependency
The Reader has an InputServicer which fetches the data by posting an HTTP request to the URL specified. The Fetch method can be tested by creating a StubInputServicer which implements a dummy Data method which is used for testing. The StubInputServicer can then be injected into the Reader in the test function.
It’s always a good practice to assert on errors. Use assert.NoError() or require.NoError() methods to assert on the error returned by the function.
Golang supports Higher-order functions, a function can use other functions as arguments and return values. This functionality of go can also be leveraged for testing. A simple use case wherein a function can be passed as an argument.
Another way is to have a package level variable which can be assigned to a function and in test it can be assigned to a dummy function. The same logic can be applied for testing a function with no return type, a package level variable can be set with the intended result and can be assert on in the test function.
Since Go is most favored for writing web applications, it provides an HTTPTest package with utilities for HTTP testing. Here’s a small Go program for checking whether a remote service is active or inactive.
The Status() function returns the status of the remote service whose endpoint is tied to the handler. HTTPTest provides a NewServer() which starts and returns a new Server. The test server URL can then be injected into the handler as the endpoint URL. So when the test function calls the Status(), it actually talks to the test server which can be made to return a specific response header and body.
That’s all folks! I’ll leave few links below for more information regarding testing in golang.