Table-Driven Tests in Go: Cleaner, Scalable Testing
What Are Table-Driven Tests?
Table-driven tests are a way to organize your test cases in a Go program. Instead of writing separate test functions for each scenario, you create a "table" (like a list) of inputs and expected outputs. This makes your tests cleaner and easier to manage.
Why Use Table-Driven Tests?
Imagine you have a function that adds two numbers. You want to test it with different inputs like (2+3), (0+5), and (-1+1). Instead of writing three separate test functions, you can use a table-driven test to handle all cases in one place. This saves time and keeps your code neat.
Benefits:
- Less repetitive code: No need to rewrite similar test logic.
- Easy to add new test cases: Just add a new row to your table.
- Clear and organized: All test cases are visible in one place.
How to Write Table-Driven Tests in Go
Let’s say we have a simple function that checks if a number is even:
func IsEven(num int) bool {
return num%2 == 0
}
Here’s how we’d test it using a table-driven approach:
func TestIsEven(t *testing.T) {
tests := []struct {
name string
input int
expected bool
}{
{"Even number", 4, true},
{"Odd number", 3, false},
{"Zero", 0, true},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result := IsEven(test.input)
if result != test.expected {
t.Errorf("Test '%s' failed: expected %v, got %v", test.name, test.expected, result)
}
})
}
}
Breaking It Down:
- We create a list (
tests
) where each entry has an input and expected output. - We loop through each test case and run it using
t.Run
. - If the result doesn’t match the expected value, we log an error.
Real-World Example: Testing a Discount Calculator
Let’s say you’re building an online store and need to test a function that applies discounts:
func ApplyDiscount(price float64, discount float64) float64 {
return price * (1 - discount/100)
}
Here’s how you’d test it with table-driven tests:
func TestApplyDiscount(t *testing.T) {
tests := []struct {
name string
price float64
discount float64
expected float64
}{
{"No discount", 100.0, 0.0, 100.0},
{"10% discount", 100.0, 10.0, 90.0},
{"50% discount", 200.0, 50.0, 100.0},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result := ApplyDiscount(test.price, test.discount)
if result != test.expected {
t.Errorf("Test '%s' failed: expected %.2f, got %.2f", test.name, test.expected, result)
}
})
}
}
Tips for Writing Good Table-Driven Tests
- Use descriptive names: Each test case should explain what it’s testing.
- Keep it simple: Avoid complex logic in test tables.
- Test edge cases: Include zero, negative numbers, or unexpected inputs.
Conclusion
Table-driven tests make your Go code easier to test and maintain. By organizing test cases in a table, you reduce repetition and make it simple to add new scenarios. Try using them in your next project!