Test Driven Development & Go

Test Driven Development & Go

ยท

6 min read

In this blog, I'll be discussing Test-Driven Development, its approaches, and best practices in the context of Golang with some code examples. Here's the GitHub repo link from where I am going to show you some code examples

Test Driven Development in short (TDD) is a software development approach where you write test cases for a small piece of code before writing the actual code. The goal is to ensure that the code meets specific requirements and behaves correctly in various scenarios. By writing tests first, you can catch errors early in the development process, and ensure that your code is easy to test, maintain, and refactor.

TDD is a cycle of writing a test, seeing it fail, writing code to pass the test, and then refactoring the code to improve its quality.

test driven development tdd - lifecycle


Motivations for TDD

  • Helps catch errors early: By writing tests before writing the actual code, TDD can help catch errors early in the development process. This reduces the time spent on debugging and ensures that your code is more reliable.

  • Ensures code meets requirements: TDD ensures that your code meets specific requirements and behaves correctly in various scenarios. This makes it easier to maintain and refactor your code as your project evolves.

  • Improves code quality: TDD encourages developers to write modular, testable, and maintainable code. This improves the overall quality of the codebase and reduces technical debt.

  • Reduces the cost of change: Since TDD ensures that your code is well-tested and modular, it reduces the cost of making changes to your codebase. This is particularly useful in large projects where changes can have significant impacts on the system.

  • Enables faster development: TDD can actually help speed up development by reducing the time spent on debugging and ensuring that new code doesn't break existing functionality.

  • Facilitates collaboration: TDD provides a common language and framework for collaboration between developers and other stakeholders. This helps ensure that everyone is on the same page and that the project moves forward smoothly.

  • Boosts confidence: TDD gives developers confidence in their code by ensuring that it behaves correctly and meets specific requirements. This confidence can lead to better decisions, more creativity, and more efficient development.

Testing in Golang

Golang provides a built-in testing tool that automates the process of running tests. The tool, called "go test", is easy to use and can be run from the command line. The Go testing package provides a range of functions and methods for creating and running tests. It includes functions for comparing values, reporting errors, and running tests in parallel.

The testing package also provides helper functions for reporting errors, such as "Error," "Errorf," and "Fail." These functions can be used to report errors that occur during the test.

Here's an example of a Test Case written in Go:

func TestSum(t *testing.T) {
    t.Run("Sum of numbers in array", func(t *testing.T) {
        numbers := []int{5, 4, 3, 2, 1}

        got := Sum(numbers)
        want := 16

        if got != want {
            t.Errorf("got %d, want %d, given %d", got, want, numbers)
        }
    })
}

Points to remember when writing test cases in Golang.

The file name for the test case must end with _test.go i.e main_test.go so that the Go tool can detect what file to execute when running go test command to run test cases.

The function for the test code must start with Test a keyword i.e TestSum. That's how the go tool knows to run the test functions written in test files.

Go's testing tool can also generate a coverage report, which shows how much of your code is covered by tests. The command for getting test coverage is

go test -cover

Go's testing package also includes support for benchmarks, which can be used to measure the performance of your code. Benchmark functions have a specific signature and are executed multiple times to provide an accurate measurement of performance. Command to run benchmarks on test cases.

go test -bench=.

Table Driven Testing

Go's testing package also supports table-driven tests, which allow you to test a function with multiple inputs and expected outputs. This can be useful for testing edge cases and ensuring that your code is robust.

Here's an example of Table Driven Test:

func TestSum(t *testing.T) {
    cases := []struct {
        description string
        num1        int
        num2        int
        expected    int
    }{
        {
            description: "1 + 2",
            num1:        1,
            num2:        2,
            expected:    3,
        },
        {
            description: "3 + 4",
            num1:        3,
            num2:        4,
            expected:    7,
        },
        {
            description: "10 + 45",
            num1:        10,
            num2:        45,
            expected:    70,
        },
    }

    for _, tt := range cases {
        t.Run(tt.description, func(t *testing.T) {
            result := Sum(tt.num1, tt.num2)
            if result != tt.expected {
                t.Errorf("expected %d, but got %d", tt.expected, result)
            }
        })
    }
}

TDD Best Practices to follow

  1. The fundamental principle of TDD is to write tests before writing the actual code.

  2. Tests should be small and focused on a single piece of functionality. This allows you to easily pinpoint errors when they occur.

  3. Use the Go testing tool to simplify and speed up your testing workflow.

  4. Table-driven tests allow you to test a function with multiple inputs and expected outputs. This can help you catch edge cases and ensure your code is robust.

  5. When writing tests that rely on external dependencies, use mocks to simulate the behavior of those dependencies. This allows you to test your code in isolation and avoid flaky tests.

  6. Use this feedback to refactor your code and make it more modular, maintainable, and testable.

  7. Make sure your tests are updated to reflect any changes you make to your code.

  8. Code coverage tools like go test -cover can help you identify areas of your code that are not adequately covered by your tests.

  9. Use a continuous integration (CI) tool to automate your testing workflow. This ensures that your tests are run regularly and that any errors are caught early in the development process.

Tools by Go Community

Go comes with its built-in testing tool, but there are some open-source tools available as well created by the Go community all around the world. Here are some of them that are mostly used in Go projects:

  1. Gomega - Matcher/Assertion lib https://github.com/onsi/gomega

  2. GoCheck - Featured rich testing lib https://github.com/go-check/check

  3. Testify - Toolkit for mocks, assertions https://github.com/stretchr/testify

  4. GoMock - A dedicated mocking framework https://github.com/golang/mock

  5. Ginkgo - A BDD testing framework for expressive specs https://github.com/onsi/ginkgo

Conclusion

Test Driven Development is a mental model where you write test cases before actual production code. It required some practice if you haven't written any test cases before. Whether you're working on real-world projects or just starting out, you can start exploring and doing hands-on on it.

If you like my article, please like and share feedback and let me know in the comments. And don't forget to follow me on Linkedin, Github, and Twitter.

Peace โœŒ๐Ÿป

ย