How to Write Unit Tests in Go
Time to read: 6 minutes
How to write unit test in go
Unit testing is a crucial aspect of software development that helps ensure the quality and reliability of your code. In the Go programming language, unit testing is particularly straightforward due to the built-in testing package provided by the standard library.
In this tutorial, you'll learn the basics of writing unit tests in Go, including table-driven tests, coverage tests, and benchmarks. You'll also see examples and learn tips for effective testing practices.
Prerequisites
Before diving into the details of unit testing in Go, the following prerequisites should be met:
- Go version 1.22 or higher
- Familiarity with programming in Go. You should have basic knowledge of Go syntax, data types, control structures, and object-oriented programming concepts.
- Your preferred text editor or IDE for writing Go
Set up your Go project
Open your terminal and create a directory for your project.
Then, run the following command to initialize a Go module. This step is crucial for managing dependencies in your project.
What we'll test: how to calculate the total amount of an order
Let's say we have an Order
struct that contains information about an order placed by a customer. The struct has three fields: ID
, CurrencyAlphaCode
, and Items
. The Items
field is a slice of Item
structs, which contain information about each product in the order. This structure will be used to perform various operations, such as iterating over the Items to calculate the order total, applying currency conversion based on the CurrencyAlphaCode
, and generating a summary of the order for the customer.
Install the required packages
You need to install the github.com/Rhymond/go-money package. This library is designed to work with monetary values using a currency's smallest unit. For our tests, we’ll use the github.com/stretchr/testify/assert package, a part of the Testify
library, which provides testing tools for the Go testing system.
Install them by running the following command:
Write the core code
Now, let's create the code that we're going to test. Create a file called order.go. Then, add the following to it:
The code above depicts a simple order management system that allows you to create orders with multiple items, and provides a method to calculate the total cost of an order.
Write the first unit test
To write a unit test for the ComputeTotal()
method, create a new file named order_test.go in the same directory as the order.go file. Then, add the following code to the file:
In this example, we define a single test case, TestComputeTotal()
, which creates an instance of the Order
struct and calls the ComputeTotal()
method. We then assert that the result matches our expected values using the assert.NoError()
, assert.Equal()
, and assert.Equal()
functions from the testify/assert
package.
To check the result in the terminal, run the following command:
You should see output similar to the screenshot below.
The "PASS" indicates that the code works as expected under the tested conditions. If a test fails, the output will show "FAIL" instead.
Add table-driven tests
Table-driven tests are a great way to simplify test cases and make them more readable. Instead of having multiple test cases for different inputs, we can define a table of input parameters and expected outputs, and run the test for all combinations.
Let's see how we can modify the previous example to use table-driven tests. Update your order_test.go file to match the following:
In the code above, we define a slice of structs called testCases
that contains two elements, each representing a test case. Each struct contains three fields: name
, order
, and expected
. The name
field is a string that describes the name of the test case. The order
field is an instance of the Order
struct that represents the input data for the test case. The expected
field is an integer representing the expected output for the test case.
We then loop through each test case using a for loop and run the test using the t.Run()
function. This function takes two arguments: the name of the test case and a function that implements the test logic. In this case, the function implementation is anonymous and defines a single assertion using the assert.Equal()
function.
To run the test, use the following command
Adding the -v
flag to the go test
command increases verbosity, providing more detailed output about which tests are running and how long each test takes. This can be particularly useful for understanding the performance of your tests and identifying any that may be taking longer than expected.
The output from running go test -v
includes information about each test, such as the name of the test, whether it passed or failed, and the time it took to run.
You should see output like this:
Add coverage tests
Coverage tests help ensure that your code covers all possible execution paths. They are essential for ensuring code quality and reliability because they help identify untested code paths, prevent potential bugs, and improve overall software robustness.
In Go, you can use the -cover
flag when running tests to generate coverage reports. A coverage report is an in-depth analysis of which parts of your code are run during tests and which are not. This information enables you to find untested code and enhance test suites.
To generate a basic coverage report, run the following code:
This command runs your tests and displays a simple coverage percentage in the terminal.
This should be the output in the terminal:
For a more detailed analysis, we can generate a coverage profile and view it in a browser.
View test results in the browser
Visualizing your test coverage is critical for understanding the quality of your code and identifying areas that could benefit from more testing. The go tool cover command is an effective technique for accomplishing this.
Visualizing your test coverage is critical for ensuring your code is well-tested and robust. It allows you to easily discover untested areas of your codebase, resulting in greater code quality and fewer defects.
First, you need to run your tests with the -coverprofile
flag to generate a coverage profile, which contains information about the coverage of your code.
This command runs your tests and generates a coverage profile file named c.out
. After this, you can then view the code in the browser using this command:
Enable benchmarks
Benchmarks measure the performance of your code. In Go, benchmarks are defined similarly to tests, but use the testing.B
type instead of testing.T
. Benchmarks are useful for identifying performance bottlenecks in your code.
For our example, we can use benchmarks to measure how long it takes to compute the total for an order with many items.
To do this, update order_test.go, with the following code:
Then, run the benchmark using this command:
You should see output like this:
The output means the loop ran 17,410,407 times at a speed of 80.09 nanoseconds per loop.
Document Go code with an example
You can document Go code using the "Example" approach. This method is part of Go's documentation practices, where examples are included directly within the codebase to serve as both documentation and executable code. They are intended to demonstrate how to use the language and its packages effectively.
To document the ComputeTotal()
function with an example, you need to first import the fmt package inside the order_test.go, and then add the example function at the end of the file, by updating the file to match the code below:
Run the go command once more:
You should see output like the following.
This feature enhances your documentation while simultaneously making your unit tests more robust.
That’s how to write a unit test in go
Unit testing in Go is straightforward, thanks to the testing package in the standard library. By following these steps, you can write effective unit tests for your Go applications, ensuring that your code behaves as expected. This makes it easier to maintain and refactor your codebase.
Remember, unit testing aims not only to find bugs but also to build confidence in your code's correctness and stability.
Temitope Taiwo Oyedele is a software engineer and technical writer. He likes to write about things he’s learned and experienced.
The testing icon in the post's main image was created by Freepik on Flaticon.
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.