Go > Testing and Benchmarking > Unit Testing > Testing with the testing package
Table-Driven Testing
This snippet demonstrates table-driven testing, a popular and efficient technique for writing unit tests in Go when you have multiple test cases for the same function.
Table-Driven Test Structure
This code defines a `Multiply` function and its table-driven test function `TestMultiply`. - A slice of structs is defined, where each struct represents a test case. Each test case includes a name, input values (`a` and `b`), and the expected result. - The test iterates through the test cases in the table. - `t.Run` creates a subtest for each test case, allowing for individual test results and easier debugging. The subtest name is taken from the `test.name` field. - Inside the subtest, the `Multiply` function is called with the input values from the current test case, and the result is compared to the expected value. - `t.Errorf` is used to report an error if the result doesn't match the expectation, including the test name and input values for easier identification of the failing case.
package mypackage
import "testing"
func Multiply(a, b int) int {
return a * b
}
func TestMultiply(t *testing.T) {
table := []struct {
name string
a, b int
expected int
}{
{"Positive Numbers", 2, 3, 6},
{"Negative Numbers", -2, 3, -6},
{"Zero", 0, 5, 0},
{"Both Negative", -2, -3, 6},
}
for _, test := range table {
t.Run(test.name, func(t *testing.T) {
result := Multiply(test.a, test.b)
if result != test.expected {
t.Errorf("Test: %s, Multiply(%d, %d) = %d; expected %d", test.name, test.a, test.b, result, test.expected)
}
})
}
}
Concepts Behind the Snippet
Table-driven testing allows you to write a single test function that executes multiple test cases. This reduces code duplication and makes your tests more organized and maintainable. The key is defining a data structure (typically a slice of structs) that holds the different input values and expected outputs for each test case.
Real-Life Use Case
Consider testing a function that validates email addresses. You can create a table of test cases that includes valid email addresses, invalid email addresses (e.g., missing `@`, invalid characters), and edge cases (e.g., very long email addresses). Table-driven testing makes it easy to add new test cases as needed.
Best Practices
Interview Tip
Be able to explain the benefits of table-driven testing compared to writing separate test functions for each scenario. Emphasize its efficiency in terms of code reuse and maintainability. Also, be prepared to discuss when table-driven testing is most appropriate (e.g., when testing a function with multiple input/output combinations).
When to Use Them
Use table-driven testing when you need to test a function with multiple different inputs and expected outputs. It's particularly useful when the logic being tested is relatively simple, but you need to cover a wide range of scenarios. It's a great way to test functions with lots of edge cases or boundary conditions.
Memory Footprint
The memory footprint is generally low, but can increase if the test table becomes very large. Ensure that the table only contains necessary data and avoids unnecessary copies of large objects.
Alternatives
The primary alternative is writing separate test functions for each test case. However, this can lead to significant code duplication and makes it harder to maintain the tests. Another alternative for complex scenarios might involve using parameterized testing libraries if they exist for Go, though standard table-driven testing is usually preferred for its simplicity.
Pros
Cons
FAQ
-
What is `t.Run` used for?
`t.Run` creates a subtest with a specific name. This allows you to run individual test cases within a larger test function and get separate results for each case. It also helps with debugging, as you can easily identify which test case failed. -
How do I handle errors within a subtest?
Use the standard `t.Errorf`, `t.Fatalf`, `t.Logf` functions within the subtest's function. `t.Fatalf` will terminate the current subtest immediately after reporting the error. `t.Errorf` continues execution of the subtest after reporting the error.