Go > Testing and Benchmarking > Unit Testing > Test functions and naming
Go Table-Driven Testing Example
Demonstrates how to use table-driven testing in Go to test multiple input/output scenarios efficiently.
Table-Driven Testing
This snippet shows how to use table-driven testing. A slice of structs defines different test cases. Each struct contains the input values (a
and b
), the expected output (expected
), and a descriptive name (name
) for the test case. The code iterates through each test case and calls t.Run
to execute a subtest with the given name. This approach allows you to test multiple scenarios without duplicating the test logic.
package main
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},
{"Mixed numbers", -2, 3, -6},
{"Zero", 0, 5, 0},
}
for _, test := range table {
t.Run(test.name, func(t *testing.T) {
result := multiply(test.a, test.b)
if result != test.expected {
t.Errorf("multiply(%d, %d) = %d; expected %d", test.a, test.b, result, test.expected)
}
})
}
}
Benefits of Table-Driven Tests
The t.Run function
The t.Run
function, introduced in Go 1.7, allows you to define subtests within a test function. This is particularly useful for table-driven tests because it allows you to run each test case as a separate subtest, which makes it easier to identify which test cases are failing. The first argument to t.Run
is the name of the subtest, and the second argument is a function that contains the test logic for that subtest.
Concepts behind the snippet
The core concept revolves around data structures. The table is a slice of structs. Each struct represents a specific test case, clearly defining the input and the expected output. This approach centralizes the test data, making it easier to review, modify, and expand the test suite as the code evolves or new scenarios are identified. The use of t.Run
further enhances the clarity of test results by providing individual success/failure notifications for each table entry.
Real-Life Use Case Section
Imagine validating user input for a form. You can use table-driven testing to verify different input types, lengths, and formats against expected outcomes (valid/invalid). You'd create a table with sample input values, expected validation results, and descriptive names like 'Valid Email', 'Invalid Email', 'Too Short Password', etc. This ensures thorough validation logic testing.
Best Practices
Interview Tip
If asked about testing in Go, demonstrate knowledge of table-driven testing. Explain its benefits and show an example of how you've used it in your projects to write more concise and readable tests. Be prepared to discuss when table-driven tests are appropriate and when other testing techniques might be more suitable.
When to use them
Table-driven tests are ideal when you need to test a function or method with multiple input values and expected outputs. This pattern is particularly useful for testing functions that perform calculations, data validation, or format conversions.
Memory footprint
The memory footprint of table-driven tests depends on the size of the table and the data types used within each test case. Keep the table as small as possible by only including the necessary test cases. Using efficient data types (e.g., ints instead of strings when possible) can also help reduce the memory footprint.
alternatives
The primary alternative is writing individual test functions for each input/output combination. However, this becomes repetitive and harder to maintain as the number of test cases increases. Fuzzing can also be an alternative, automatically generating inputs to find edge cases, but it's more suitable for finding unexpected behavior than verifying specific outputs.
pros
cons
FAQ
-
How do I handle more complex test cases with table-driven testing?
For more complex test cases, consider using helper functions to encapsulate the test logic within each test case. This can help keep the table concise and improve the readability of the tests. -
Can I use table-driven testing with external dependencies?
Yes, you can use table-driven testing with external dependencies. However, you may need to use mocking or other techniques to isolate the code being tested from the dependencies. Use interfaces to abstract your dependencies. -
Is table driven testing appropriate for ALL tests?
No. Table-driven tests are most appropriate when testing functions with a clear set of inputs and expected outputs. For more complex scenarios or integration tests, other testing techniques may be more appropriate. -
How can I test for errors in table driven tests?
Include anerror
field in your struct and check the returned error against the expected value usingerrors.Is
orerrors.As
. You can have boolean fields in your struct to assert error type and not nil value.