on
Testing with Go
An integral part of the development licefycle is writing tests. Write tests early, write tests often as they say. Go makes it very easy to write tests. The following post will go through the basics of testing with Go.
Let’s say we have a program that counts the number of vowels, consonants, digits and everything else within an input string. It may look something like this:
|
|
Go provides a testing package to help with writing unit tests. Unit tests are contained in files typically ending in *_test.go
. So to test the main function, you would have main_test.go
in the same directory and so on. Files whose names begin with _
or .
are ignored. There are three types of functions that are typically contained in a test file: TestXxx
, ExampleXxx
, BenchmarkXxx
TestXxx
These are your standard unit tests. They should have a signature like: func TestXxx(t *testing.T)
. Note: Xxx
indicates the test name and has to begin with a capital letter, else no tests will get run.(typically indicated by a warning like: testing: warning: no tests to run
)
Go has popularized TableDrivenTests. It lists out the inputs / desired outputs for various test cases as an array of structs and then just cycles through these tests checking to see if the tests passed. It leads to very concise and easily understandable unit test code. Here is what a main_test.go
could look like:
|
|
ExampleXxx
These type of tests are primarily aids to documentation. When documentation of your package is generated via godoc
, these examples are extracted and displayed so that the user can understand things more clearly. You can see many examples in the standard go packages
A few points to note about these example tests:
- They take no input
- They are determined to PASS if the stdout matches the output specified in the example, else they FAIL
- If the output is not specified, the tests are compiled but not run
Here is what it would look like:
|
|
BenchmarkXxx
These tests are used to gain timing info of functions. We essentially place the function we wish to benchmark in a loop and run it b.N
times. (This value is determined by the benchmark runner)
|
|
Keeping track of the benchmark results over time can be a good way to track drift in performance of your codebase.
Running tests
An easy way to run the above test is by using a Makefile
. It could look something like:
.PHONY : test
test:
@cd cmd/gocountchars && go test -bench . -short -v
Here we have supplied the -short
argument to indicate that we don’t want to run long tests. Hence inside our test code we can test to see if this flag is on via testing.Short()
and act accordingly (as shown in the example above). The -v
flag is for verbosity. Then, to run the tests: make test
The advantage of using Makefiles is that it can be easily integrated into other tools such as Jenkinsfile also. Running the tests will show an output like:
=== RUN TestCountChars
--- SKIP: TestCountChars (0.00s)
main_test.go skipped test as we are in a hurry
=== RUN ExampleCountChars
--- PASS: ExampleCountChars (0.00s)
goos: darwin
goarch: amd64
pkg: github.com/jimmyislive/gocountchars/cmd/gocountchars
BenchmarkCountChars-8 30000000 58.3 ns/op
PASS
ok github.com/jimmyislive/gocountchars/cmd/gocountchars 1.833s
All code for this can be found on Github