An Introduction to Testing in Go
🚀 My new course - The Golang Testing Bible is out now and covers everything you need to get up and running creating tests for your Go applications!
Testing is hugely important in all software. Being able to ensure the correctness of your code and ensure that any changes you make don’t end up breaking anything else in different parts of your codebase is hugely important.
By taking the time to adequately test your go programs you allow yourself to develop faster with a greater sense of confidence that what you are developing will continue to work when you release it to production.
Goals
By the end of this tutorial, you will have a good grasp of testing basic functions
and methods in Go using the standard "testing"
package.
You will have had experience writing table-driven tests and you will also see how to generate more verbose output from your tests using the various flags available.
Video Tutorial
This tutorial can be found in video format if you prefer:
Introduction
In this tutorial we are going to look at how you can develop and run tests for
your go code using the go test
command.
Go Test Files
If you have seen any go projects before, you may have noticed that most, if not
all files within the project, feature a FILE_test.go
counterpart within the
same directory.
This is not an accident. These are the files which contain all of the unit tests for the project and they test all of the code within their counterparts.
myproject/
- calc.go
- calc_test.go
- main.go
- main_test.go
A Simple Test File
Imagine we had a very simple go program that was made up of one file and
featured a calculate()
function. This calculate()
function simply takes in 1
number and adds 2 to it. Nice and simple to get us up and running:
package main
import (
"fmt"
)
// Calculate returns x + 2.
func Calculate(x int) (result int) {
result = x + 2
return result
}
func main() {
fmt.Println("Hello World")
}
If we wished to test this we could create a main_test.go
file within the same
directory and write the following test:
package main
import (
"testing"
)
func TestCalculate(t *testing.T) {
if Calculate(2) != 4 {
t.Error("Expected 2 + 2 to equal 4")
}
}
Running Our Tests
Now that we have created our first go test, it’s time to run this and see if our code behaves the way we expect it to. We can execute our tests by running:
$ go test
This should then output something similar to the following:
PASS
ok _/Users/elliot/Documents/Projects/tutorials/golang/go-testing-tutorial 0.007s
Table Driven Testing
Now that we are happy that one calculation works, we should look to improve
confidence by adding a few extra test cases into our code. If we want to
gradually build up a series of test cases that are always tested, we can
leverage an array
of tests like so:
func TestTableCalculate(t *testing.T) {
var tests = []struct {
input int
expected int
}{
{2, 4},
{-1, 1},
{0, 2},
{-5, -3},
{99999, 100001},
}
for _, test := range tests {
if output := Calculate(test.input); output != test.expected {
t.Error("Test Failed: {} inputted, {} expected, recieved: {}", test.input, test.expected, output)
}
}
}
Here we declare a struct that contains both an input and the expected value. We
then iterate through the list of tests with our for _, test := range tests
call and check to see that our function will always return the expected results,
regardless of input.
When we run our test suite now, we should see the same output as before:
PASS
ok _/Users/elliot/Documents/Projects/tutorials/golang/go-testing-tutorial 0.007s
Verbose Test Output
Sometimes you may wish to see exactly what tests are running and how long they
took. Thankfully, this is available if you use the -v
flag when running your
tests like so:
=== RUN TestCalculate
--- PASS: TestCalculate (0.00s)
=== RUN TestTableCalculate
--- PASS: TestTableCalculate (0.00s)
PASS
ok _/Users/elliot/Documents/Projects/tutorials/golang/go-testing-tutorial 0.006s
You can see that both our normal test and our table test ran and passed and took
less than 0.00s
to execute.
Checking Test Coverage
Test coverage is a metric that I have seen abused at times by companies. These companies set targets on all of their systems such as “the codebase must be at least 75% covered by tests”.
But these kind of targets can lead to poor behavior and development teams “gaming” the system in order to try and hit these targets. I’ve seen people in some teams spend days writing unit tests that test basic getters and setters in languages such as Java.
Testing is hugely important, but you have to be pragmatic about how you test your systems so that the tests you write are providing you with the most value.
The days spent writing tests to cover unimportant parts of your codebase could have been better spent writing test cases around the critical business logic captured within your systems and ensuring more edge cases are covered.
Using the -cover flag
With the important part out of the way, let’s look at how you can check the
test coverage of your system using the go test
command:
Within the same directory as your main.go
and your main_test.go
files, run
the following:
PASS
coverage: 66.7% of statements
ok github.com/TutorialEdge/an-intro-to-testing-in-go 0.006s
You will see that you have 66.7%
of your total Go code covered by test
cases.
Visualizing Coverage
Whilst this 66.7%
value can tell us how much of our code we have tested,
it doesn’t show us exactly what code paths we have or haven’t tested.
This is where the go test
and the go tool cover
come in to help us solve
this particular problem.
We can use the go test
tool to generate a coverprofile
which can then be
converted to a HTML visualization using the go tool cover
command:
PASS
coverage: 66.7% of statements
ok github.com/TutorialEdge/an-intro-to-testing-in-go 0.008s
You can then take this generated coverage.out
file and use it to generate
a HTML page which shows exactly what lines have been covered like so:
$ go tool cover -html=coverage.out
This will open up a page in your browser of choice which will look a little something like this:
As you can see, most of the code within our Calculate
function is testing and
features Green
coverage. Whilst the print statement in your main
function
is Red
as this hasn’t been covered by tests.
Conclusion
Hopefully you found this tutorial useful! If you require further assistance then please feel free to let me know in the comments section below.
The full source code for this article can be found here: TutorialEdge/an-intro-to-testing-in-go
Further Reading
If you enjoyed this, you may like my other articles on testing in Go: