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:

  1. We create a list (tests) where each entry has an input and expected output.
  2. We loop through each test case and run it using t.Run.
  3. 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!