How to Test Code in Go
Language
- unknown
by James Smith (Golang Project Structure Admin)
Testing should be considered an essential part of the software-development process, because it helps to ensure that our code works as expected when handling a wide range of reasonable values as input.
So today we are going to look at how to write tests in Go, using the built-in syntax, and how to run the tests — as well as how to use other testing frameworks.
Table of Contents
Why Bother Writing Tests in Go?
The Go programming language has built-in support for unit testing, which makes it extremely easy to write and run tests for our code.
The central idea behind unit testing is that code can be broken down into small sections, known as units, which may consist of individual functions or methods, and that these can — and should — be tested in insolation from the rest of your application. A function could tested by feeding example inputs into it and checking that the outputs make sense.
By writing tests as we develop our codebase, we can find bugs that may cause run-time errors before they make it into production-ready software.
This also allows us to notice problems that could arise in a function that previously used to work correctly when modifying its code. Whenever major modifications are made to any section of code, it should be tested to ensure that it still meets its requirements successfully.
How to Write Unit Tests in Go
All of the functions that are used to perform unit tests in Go are written in a separate files that each have the suffix _test.go
.
For example, if we have a file called calculation.go
, then our corresponding test file should be called calculation_test.go
.
Test functions are written with the help of the “testing” package, which is part of Go’s standard library.
As an example, let’s define a simple function that we can later write a unit test for:
calculation.go
package calculation
func AddAbs(a, b int) int {
var sum int
if a >= 0 {
sum += a
} else {
sum += -a
}
if b >= 0 {
sum += b
} else {
sum += -b
}
return sum
}
The AddAbs
function adds the absolute values of two integers together and returns the sum.
This means that if either of the two integers that are given as input is negative, it will be made positive before it is added to the other one. If both of the integers are positive already, then they will simply be added together.
So the function will always return a positive number.
Now that we’ve created the AddAbs
function, let’s write a unit test for it:
calculation_test.go
package calculation
import "testing"
func TestAddAbs(t *testing.T) {
result := AddAbs(-1, 9)
if result != 10 {
t.Errorf("AddAbs(-1, 9) == %d; expected 10", result)
}
result = AddAbs(-50, -25)
if result != 75 {
t.Errorf("AddAbs(-50, -75) == %d; expected 75", result)
}
}
The t.Errorf
method works like the fmt.Printf
function that we discussed in detail recently, except that the output is logged to os.Stderr
rather than os.Stdout
.
Notice how the calculation_test.go
file uses the same package name as the calculation.go
.
It is also possible to write test files in a separate package that has a name ending with the suffix _test
(so, in this case, calculation_test
). However, if we’d done this, we would have needed to import the calculation
package in order to use the AddAbs
function.
How to Run Unit Tests in Go
In order to run any tests that we have written, we simply run the go test
command.
This command looks for all of the test files within the current directory and reads them, running all of the functions within those files that have names starting with the prefix Test
(such as TestAddAbs
that we defined above).
It’s also possible to run only the tests that match a specific pattern, by giving a pattern as an argument to the -run
option as shown in the command below:
go test -run=AddAbs
Introducing Testing Frameworks in Go
While the native "testing"
package is sufficient for most purposes, Go also has many testing frameworks that have been created by third parties with the aim of making it easier and more efficient to write tests.
These frameworks often provide additional functionality and features that can help to streamline the entire testing process.
Using the Testify Framework in Go
Testify is one of the most commonly used testing frameworks in Go.
Before using Testify, we should ensure that we have the most recent version installed by running the following command:
go get -u github.com/stretchr/testify
Now let’s rewrite our original test function, using the Testify framework:
package mycode
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestAdd(t *testing.T) {
result := AddAbs(-1, 9)
assert.Equal(t, 10, result, "AddAbs(-1, 9) should equal 10")
result = AddAbs(-50, -25)
assert.Equal(t, 75, result, "AddAbs(-50, -25) should equal 75")
}
The asset.Equal
function simply checks that an expected value equals an actual value and prints a given error message if it doesn’t. This reduces the need for us to include any conditional logic in our code, as we had with the initial test function that we wrote.
Notice, however, how we still use the native "testing"
package in order to pass the testing.T
pointer as an argument to the assert.Equal
function.
What Are Assertions and How Are They Used in Testing?
The assert.Equal
function that the Testify framework provides is an example of an assertion.
Assertions are statements in a program that declare — or, in other words, assert — that a certain condition must be true.
Each assertion is used to verify that the behaviour of a section of code works as expected.
Testify’s "assert"
package also contains functions such as NotEqual
(which declares that two values must not equal one another), Nil
(which declares that a value must be nil) and NotNil
(which declares that a value must not be nil).
Using the Ginko Testing Framework
Ginkgo is an alternative testing framework that is based on the idea of Behaviour-Driven Development (BDD), providing a more expressive way to write tests.
Behaviour-Driven Development focuses on describing what goals each test should achieve.
This means that Ginkgo allows us to write tests using descriptive language that reads somewhat like English.
Gomega is Ginkgo’s assertion library.
When used together, Ginkgo and Gomega can be thought of as a single domain-specific language (DSL).
We can install the latest versions of Ginkgo and Gomega by running the following commands:
go get -u github.com/onsi/ginkgo/v2
go install github.com/onsi/ginkgo/v2/ginkgo
go get -u github.com/onsi/gomega/...
Now let’s look at an example of the Ginkgo testing framework in action:
package calculation
import (
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestCodeWithGinko(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Ginko Test Suite")
}
var _ = Describe("AddAbs", func() {
It("should add the absolute value of two integers together", func() {
result := AddAbs(-99, 2)
Expect(result).To(Equal(101))
})
})
We begin, in the above example, by importing both the Ginkgo and Gomega packages.
We use a dot before the path to each import, because we want the exported functions to act as though they are part of the current package, allowing us to use their names without a qualifier.
This is just a matter of style, so it would have been perfectly possible to import the packages the normal way, but this would have the side effect of making the imported functions read less like plain English, because we’d have to keep repeating the package names.
The Describe
function takes as its first argument a string that describes the nature of the tests that occur within the callback function given as its second argument.
Likewise, the It
function takes as its first argument a string that describes what should be expected from a specific unit test that occurs within the callback function given as its second argument.
In order to run the Describe
function without storing its return value, we assign it to an underscore variable, which is known as the blank identifier.
The Expect
and Equal
functions are provided by the Gomega library, and they help us to assert an expected result for our unit test. In this case, the test will fail if the result of AddAbs(-99, 2)
does not equal 101
.
If you want to learn more about the Ginkgo test framework and how it can be used to write more complex sequences of unit tests (by, for example, running a section of setup code before each test), I suggest that you read the excellent documentation provided by Onsi Fakhouri, the developer who created the framework.