Testing is a crucial aspect of software development that helps ensure the quality and reliability of your code. In Go, testing is built into the language through the testing
package, which makes it easy to write and execute tests for your programs.
Tests are essential because they:
- Validate the correctness of your code, ensuring that it behaves as expected under various conditions.
- Provide a safety net for future modifications, allowing you to make changes with confidence that you won’t inadvertently break existing functionality.
- Act as documentation complement for other developers, demonstrating how the code is supposed to work and serving as a guide for using and modifying it.
- Facilitate better code design, as writing tests often encourages developers to create modular and reusable components.
For Go developers, incorporating tests into the development process can lead to more reliable and maintainable code, ultimately improving the overall quality of your applications.
In this article, you’ll learn how to write and run table-driven tests and benchmarks in Go. If you’re new to testing or feel uncertain about the topic, read the Introduction to Testing in Go topic on Hyperskill, which covers the basics of tests in Go.
Table-driven tests are a typical pattern in Go for testing multiple input and output cases using a single test function. Instead of writing separate test functions for each case, you can define a table (a slice of structs) that includes the input values, expected output, and an optional description for each test case. You can then loop through the table and execute the test function for each case.
Compared to individual unit tests, table-driven tests offer several advantages:
- They reduce code duplication and make your test suite more maintainable.
- They make it easy to add new test cases, as you simply need to extend the table.
- They provide a clear overview of the various input-output combinations being tested.
Let’s get started! First, create a new Go project named example
and initialize Go modules via the following commands:
mkdir example && cd example
go mod init example
Then create a new file main.go and within it, let’s write the code of the DiscountedPrice()
function that calculates the discounted price of a product:
The next step is to create a new file main_test.go, and within it, write a table-driven test for the DiscountedPrice()
function:
To run the tests, you can use the go test
command to execute all test functions in the package and report the results. By default, go test
runs all tests for the current package. However, you can also provide a package name, a directory, or a list of packages to test multiple packages simultaneously.
After executing the go test
command, you should see the following output:
> go test
PASS
ok example 0.236s
PASS
indicates all the tests have passed, ok
confirms that the text execution was successful, example
is the name of the Go module being tested, and 0.236s
is the duration of the test execution in seconds.
Even though all tests passed, how can you be sure there were no untested code paths left? The answer is simple you will need to check the test coverage.
Test coverage is a metric that measures the proportion of your code that is exercised by your test suite. High test coverage indicates that your tests are comprehensive, while low test coverage suggests that some parts of your code may not be adequately tested. By tracking test coverage, you can identify untested portions of your code and write additional tests to improve the reliability of your application.
To run the tests with coverage, use the go test -cover
command. It will execute all test functions in the package, report the results, and provide a coverage percentage indicating the proportion of your code exercised by your tests.
> go test -cover
PASS
coverage: 85.7% of statements
ok example 0.253s
After examining the output, the coverage is only 85.7%
, which indicates that one of the code paths wasn’t tested. Upon closer inspection, you might notice the absence of a test case for the “negative discount” scenario.
To improve test coverage, you can add the “negative discount” test case {100.0, -10.0, 0.0, true, “negative discount”}
to the testCases
slice: