How to Write Testable Business Code in Go: A Practical Guide
The guide shows that by consciously designing Go data structures and function interfaces—using pure functions, parameter injection, interface abstractions, struct‑based objects, or function‑variable patterns—and avoiding init(), developers can expose and control all dependencies, making business code inherently testable and achieving high unit‑test coverage.
This article addresses the common challenge of writing unit tests in long-term iterative projects. While teams acknowledge the importance of unit testing, many find it burdensome due to the extra time and effort required to set up test environments, initialize variables, and mock dependencies.
The author argues that with conscious design of data structures and function interfaces, code can be made easily testable without any special tricks. The key is understanding that testable code and logically clear code are different—logically clear code isn't necessarily easy to test.
Pure Functions: A pure function satisfies three conditions: same inputs always produce same outputs, no side effects, and no external dependencies. Pure functions are easiest to test using table-driven testing approaches.
Dependency Extraction: The fundamental principle is making all function dependencies controllable. Three main approaches are discussed:
Parameter Injection: Pass all dependencies as function parameters. For example, func NHoursLater(n int64, now time.Time) time.Time instead of using time.Now() internally.
Interface-based Design: Use interfaces to abstract dependencies, allowing easy mocking. Define a Queryer interface with a Query method, then implement mock versions for testing.
Object-Oriented Approach: Store dependencies as struct fields. Create objects with all necessary dependencies, making them controllable during testing.
Function Variable Pattern: Store function pointers in variables (e.g., var infoContextf = log.InfoContextf ), allowing replacement during tests.
Avoiding init(): The article strongly recommends avoiding Go's init() function in business code because it executes before tests run and can cause panics if it tries to connect to real services or read configuration files that don't exist in test environments.
Summary: The two guiding principles are: 1) Identify all function dependencies (both explicit and implicit), and 2) Extract dependencies to make them controllable from outside the function. Following these patterns makes writing testable code straightforward and enables high test coverage.
Tencent Cloud Developer
Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.