8. Have you written unit tests in Go before? How do you ensure code coverage in your tests?

Basic

8. Have you written unit tests in Go before? How do you ensure code coverage in your tests?

Overview

In Go programming, writing unit tests is a fundamental practice that ensures your code behaves as expected. Ensuring code coverage in tests is crucial for identifying untested paths and improving the reliability of your software. This guide focuses on Go's approach to unit testing and strategies for maximizing code coverage.

Key Concepts

  • Testing package: Go's built-in package for writing test cases.
  • Table-driven tests: A common pattern in Go for writing concise and comprehensive tests.
  • Code coverage: A metric used to measure the percentage of code executed by tests.

Common Interview Questions

Basic Level

  1. How do you write a simple unit test in Go?
  2. What is the command to run all tests in a Go package?

Intermediate Level

  1. How do you write table-driven tests in Go?

Advanced Level

  1. How do you use the testing package to mock dependencies in Go tests?

Detailed Answers

1. How do you write a simple unit test in Go?

Answer:
In Go, a simple unit test is written in a _test.go file using the testing package. You define a function with a name starting with Test, followed by a name that describes the test case. This function takes a single argument, t *testing.T, used for reporting test failures.

Key Points:
- Test files should be in the same package as the code they test.
- Use t.Errorf to report test failures.
- Follow the convention of naming test functions as TestXxx, where Xxx does not start with a lowercase letter.

Example:

package mypackage

import "testing"

func TestSum(t *testing.T) {
    result := Sum(1, 2)
    expected := 3
    if result != expected {
        t.Errorf("Sum was incorrect, got: %d, want: %d.", result, expected)
    }
}

2. What is the command to run all tests in a Go package?

Answer:
To run all tests in a Go package, use the go test command in the terminal within your package directory. This command compiles the test files and runs each test function defined in those files.

Key Points:
- Runs all tests in the current package by default.
- Can also specify package names to test multiple packages.
- Use go test ./... to recursively test all packages in your project.

Example:
To run tests in the current package:

go test

To run tests in all subdirectories:

go test ./...

3. How do you write table-driven tests in Go?

Answer:
Table-driven tests in Go use a slice of structs, where each struct represents a test case with input values and expected results. This pattern allows for easily adding new test cases and keeps the test code DRY (Don't Repeat Yourself).

Key Points:
- Each table entry is a test case.
- Loop over the table and run the test logic for each case.
- Clearly separates test data from test logic.

Example:

package mypackage

import "testing"

func TestSumTableDriven(t *testing.T) {
    var tests = []struct {
        a, b int // Input
        expected int // Expected result
    }{
        {1, 2, 3},
        {2, 3, 5},
        {5, -1, 4},
    }

    for _, tt := range tests {
        testname := fmt.Sprintf("%d+%d", tt.a, tt.b)
        t.Run(testname, func(t *testing.T) {
            result := Sum(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("got %d, want %d", result, tt.expected)
            }
        })
    }
}

4. How do you use the testing package to mock dependencies in Go tests?

Answer:
To mock dependencies in Go tests, you typically define an interface that describes the dependency's behavior. Then, for testing, you create a mock implementation of the interface that you can control in your tests. This approach is useful for isolating the unit of work and testing it independently from its dependencies.

Key Points:
- Define interfaces for your dependencies.
- Implement mock versions of these interfaces for testing.
- Use dependency injection to use the real or mock dependencies in your code.

Example:

package mypackage

import "testing"

// Dependency interface
type Adder interface {
    Add(a, b int) int
}

// Mock implementation
type MockAdder struct{}

// Mock method
func (m *MockAdder) Add(a, b int) int {
    // Mock behavior here
    return a + b // Simplified for example
}

// Function under test that uses the dependency
func UseAdder(a Adder) int {
    return a.Add(1, 2)
}

// Test function
func TestUseAdder(t *testing.T) {
    mockAdder := &MockAdder{}
    result := UseAdder(mockAdder)
    expected := 3
    if result != expected {
        t.Errorf("UseAdder was incorrect, got: %d, want: %d.", result, expected)
    }
}