How to write unit tests in GO?
You are done with your project/code and it looks great. What next? The next thing to do is to test your code to be sure it is doing what it is supposed to do. In software development we have unit testing which is basically a test written by a software developer to ensure function or classes meet its desired design and behave and operate as intended. You can test an entire module or just a function in your code. The main purpose of having tests in your code is to isolate each part of your program and demonstrate that these individual units of your code actually work as intended. Unit tests help identify problems that might be shipped to production if no thorough tests are done.
Software testing by developers is so vital that has led to a development process called Test Driven Development or TDD. TDD is a development process that requires that software requirements get converted to test cases before software is fully developed, and track all software development by repeatedly testing the software against all test cases. This is unlike what you would expect where software is developed first and tests are written later. This article will mainly focus on unit testing and help get started with writing tests in Go.
Prerequisites
- Basic Go language knowledge.
- Go runtime version 1.11 or higher installed on your machine.
Best Practices for Golang Testing
Go comes with a built-in testing package called “testing” .To be able to use this package, below requirements must be met .
- The filename containing the test must end with “
_test.go
” . For example ifdemo_test.go
. The testing package checks for filename ending with_test.go
to run the actual test. - In the testing file, make sure to import the testing package. Without the testing package, the test can not be run using the “go test” command.
- The testing file must have a function(s) that starts with the “Test” word followed by a name describing what kind of test you want to have. For example if you are testing a function that adds two numbers, the function name will be something like
TestSumOfTwo()
. Please note that the name describing what the test will do has to start with uppercase letter i.eTestSumOfTwo()
. - The test function takes as an argument, a pointer to the testing package. Therefore the function will look like
func TestSumOfTwo(*testing.T)
- To run the tests, use the “go test” in the terminal . This command has to be run in the same module as the testing file.
The testing file should be put in the same package as the code being tested. This package will not be included while building your packages. It will be excluded when the go test command is run. Therefore do not be worried because these test codes will not be included in deployment code.
How to test in Go
In this section , we dive deep into how to get started with writing tests in Go. Using a code editor of your choice, I prefer VScode editor and I highly recommend it.Using a terminal follow the below instructions to get up and running.
Create a working directory
$ mkdir go_test
Move into the go_test
directory
$ cd go_test/
Initialize a go module
$ go mod init example.com/go_test
Create a main.go
file
touch main.go
Now that all that is set up, let's begin to code. We will start off by writing functions that perform addition, subtraction , division and multiplication of numbers . Because these are basic math operations, we will create another folder at the root of our application called math. The math module will contain all the basic math operations functions together with the testing code. In your root directory issue the below commands.
Create a math folder with math.go and math_test.go files.
$ mkdir math && cd math && touch math.go math_test.go
math.go
package math
func Addition(a, b int) int {
return a + b
}
func Subtraction(a, b int) int {
return a - b
}
func Multiplication(a, b int) int {
return a * b
}
func Division(a, b int) float64 {
return float64(a / b)
}
math_test.go
package math
import "testing"
func TestAddition(t *testing.T) {
want := 10
ans := Addition(5, 7)
if ans != want {
t.Fatalf("got %d, wanted %d", ans, want)
}
}
func TestSubtraction(t *testing.T) {
want := 0
ans := Subtraction(5, 5)
if ans != want {
t.Fatalf("got %d, wanted %d", ans, want)
}
}
func TestMultiplication(t *testing.T) {
want := 25
ans := Multiplication(5, 5)
if ans != want {
t.Fatalf("got %d, wanted %d", ans, want)
}
}
func TestDivision(t *testing.T) {
want := 1.0
ans := Division(5, 5)
if ans != want {
t.Fatalf("got %f, wanted %f", ans, want)
}
}
Explanation
The above unit test looks very similar in terms of code execution. We first of all start identifying the package that our code is hosted in, i.e. package math
. We then import the testing package with import “testing”.
In the unit test, we define the functions that all start with the TestXxx
format and all take a pointer to the testing package TestXxx(t *testing.T)
.
Since we already know what results we want for each and every unit test, we assign the <b>want</b>
variable to the expected result for each math operation. The <b>ans</b>
variable gets assigned the response to each respective function that we are testing. Please note that we do not import the Addition
, Subtraction
, Multiplication
and Division
functions. In Go if a function starts with a capital letter, it is visible to any code in the same package. In this case,math
is the package.
We then compare each response from these respective functions with the expected results.
Example
if ans != want {
t.Fatalf("got %q, wanted %q", ans, want)
}
Before running our test, we need to add some code in our main.go file. The code in main.go basically runs the functions in the math package.
main.go
package main
import (
"fmt"
math "example.com/go_test/math"
)
func main() {
fmt.Println("Go testing ...")
a := 5
b := 5
sum := math.Addition(a, b)
sub := math.Subtraction(a, b)
mul := math.Multiplication(a, b)
div := math.Division(a, b)
fmt.Printf("The sum of %d and %d is %d \n", a, b, sum)
fmt.Printf("The subtraction of %d from %d is %d \n", a, b, sub)
fmt.Printf("The multiplication of %d by %d is %d \n", a, b, mul)
fmt.Printf("The division of %d and %d is %f \n", a, b, div)
}
Output:
To run the code in the main.go file, navigate to the root of your application if you haven't already and issue the below command.
$ go run main.go
Go testing ...
The sum of 5 and 5 is 10
The subtraction of 5 from 5 is 0
The multiplication of 5 and 5 is 25
The division of 5 and 5 is 1.000000
You will agree with me that this is not how we run the tests, that is true. The above command pretty much tests if our program runs successfully and there are no runtime errors.
Running tests
To run the tests, navigate to the math folder and issue the below command.
$ go test
PASS
ok example.com/go_test/math 0.002s
The above output shows that all the tests have run and passed successfully. This is what we want, but you can print more information about each individual unit test by adding -v
in the go test command. The v
stands for verbose.
$ go test -v
=== RUN TestAddition
--- PASS: TestAddition (0.00s)
=== RUN TestSubtraction
--- PASS: TestSubtraction (0.00s)
=== RUN TestMultiplication
--- PASS: TestMultiplication (0.00s)
=== RUN TestDivision
--- PASS: TestDivision (0.00s)
PASS
ok example.com/go_test/math 0.002s
Testing negative scenarios
In order to test the correctness of a function, we can pass values of types that these functions do not allow or expect. For example, the Addition() function expects the two arguments passed to it to be integers, but we can pass a string just to see if the test will fail or not.
Example
func TestAddition(t *testing.T) {
want := 10
ans := Addition(5, "5")
if ans != want {
t.Fatalf("got %d, wanted %d", ans, want)
}
}
Output
$ go test
# example.com/go_test/math [example.com/go_test/math.test]
./math_test.go:8:21: cannot use "5" (untyped string constant) as int value in argument to Addition
FAIL example.com/go_test/math [build failed]
Explanation
In the above example, we pass a string “5”
to the Addition function and run the test again using the go test -
v command. The above test fails because “5” is not the correct type to be passed to the Addition function and it is not possible to perform addition of string and int types.
Summary
This article introduces you to testing in go using the built in “testing” package. Testing software is a key ingredient for a robust software and there it is important to add tests to your code before shipping them to production. So far we have looked at testing code manually, but this process can be automated by remote servers like CircleCi and many others.
References
https://go.dev/doc/tutorial/add-a-test
https://pkg.go.dev/testing