go-unit-testing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseUnit Testing
单元测试
Instructions for AI coding agents on automating unit test creation using consistent software testing patterns in this Go project.
针对AI编码代理的说明:在本Go项目中使用统一的软件测试模式自动化单元测试创建。
1. Benefits
1. 优势
-
ReadabilityEnsures high code quality and reliability. Tests are self-documenting, reducing cognitive load for reviewers and maintainers.
-
ConsistencyUniform structure across tests ensures predictable, familiar code that team members can navigate efficiently.
-
ScalabilityTable-driven and data-driven approaches minimize boilerplate code when adding new test cases, making it simple to expand coverage.
-
DebuggabilityScoped traces and detailed assertion messages pinpoint failures quickly during continuous integration and local testing.
-
可读性确保高代码质量和可靠性。测试具备自文档性,降低评审人员和维护人员的认知负担。
-
一致性测试采用统一结构,代码可预测且易于理解,团队成员能高效浏览。
-
可扩展性表格驱动和数据驱动方法在添加新测试用例时最大限度减少样板代码,便于扩展测试覆盖率。
-
可调试性范围化的跟踪信息和详细的断言消息能在持续集成和本地测试中快速定位故障。
2. Patterns
2. 测试模式
2.1. In-Got-Want
2.1. In-Got-Want
The In-Got-Want pattern structures each test case into three clear sections.
-
InDefines the input parameters or conditions for the test.
-
GotCaptures the actual output or result produced by the code under test.
-
WantSpecifies the expected output or result that the test is verifying against.
In-Got-Want模式将每个测试用例分为三个清晰的部分。
-
In定义测试的输入参数或条件。
-
Got捕获被测代码产生的实际输出或结果。
-
Want指定测试要验证的预期输出或结果。
2.2. Table-Driven Testing
2.2. 表格驱动测试
Table-driven testing organizes test cases in a tabular format, allowing multiple scenarios to be defined concisely.
-
Test Case StructureEach row in the table represents a distinct test case with its own set of inputs and expected outputs.
-
IterationThe test framework iterates over each row, executing the same test logic with different data.
表格驱动测试以表格形式组织测试用例,可简洁定义多个测试场景。
-
测试用例结构表格中的每一行代表一个独立的测试用例,包含自身的输入和预期输出。
-
迭代执行测试框架遍历每一行,使用不同数据执行相同的测试逻辑。
2.3. Data-Driven Testing (DDT)
2.3. 数据驱动测试(DDT)
Data-driven testing separates test data from test logic, enabling the same test logic to be executed with multiple sets of input data.
-
External Data SourcesTest data can be stored in external files (e.g., JSON, CSV) and loaded at runtime.
-
ReusabilityThe same test logic can be reused with different datasets, enhancing maintainability and coverage.
数据驱动测试将测试数据与测试逻辑分离,使同一测试逻辑可通过多组输入数据执行。
-
外部数据源测试数据可存储在外部文件(如JSON、CSV)中,并在运行时加载。
-
可复用性同一测试逻辑可与不同数据集复用,提升可维护性和测试覆盖率。
2.4. Arrange, Act, Assert (AAA)
2.4. 准备-执行-断言(AAA)
The AAA pattern structures each test case into three clear phases.
-
ArrangeSet up the necessary preconditions and inputs for the test.
-
ActExecute the function or method being tested.
-
AssertVerify that the actual output matches the expected output.
AAA模式将每个测试用例分为三个清晰的阶段。
-
Arrange(准备)设置测试所需的前置条件和输入。
-
Act(执行)执行被测函数或方法。
-
Assert(断言)验证实际输出是否与预期输出匹配。
2.5. Test Fixtures
2.5. 测试夹具
Test fixtures provide a consistent and reusable setup and teardown mechanism for test cases.
-
SetupInitialize common objects or state needed for multiple tests.
-
TeardownClean up resources or reset state after each test.
测试夹具为测试用例提供一致且可复用的初始化和清理机制。
-
初始化初始化多个测试所需的通用对象或状态。
-
清理在每个测试完成后清理资源或重置状态。
3. Workflow
3. 工作流程
-
IdentifyIdentify new functions inor
pkg/(e.g.,internal/).pkg/<package>/<file>.go -
Add/CreateCreate new tests in the same package (e.g.,).
pkg/<package>/<file>_test.go -
Test Coverage RequirementsInclude comprehensive edge cases:
- Coverage-guided cases
- Boundary values (min/max limits, edge thresholds)
- Empty/null inputs
- Null pointers and invalid references
- Overflow/underflow scenarios
- Special cases (negative numbers, zero, special states)
-
Apply TemplatesStructure all tests using the template pattern.
-
识别识别或
pkg/中的新函数(例如:internal/)。pkg/<package>/<file>.go -
添加/创建在同一包中创建新测试(例如:)。
pkg/<package>/<file>_test.go -
测试覆盖率要求需包含全面的边缘场景:
- 覆盖率导向的测试用例
- 边界值(最小/最大限制、边缘阈值)
- 空/空值输入
- 空指针和无效引用
- 溢出/下溢场景
- 特殊情况(负数、零、特殊状态)
-
应用模板使用模板模式构建所有测试。
4. Commands
4. 命令
| Command | Description |
|---|---|
| Execute tests with race detection and JUnit report |
| Generate coverage reports (HTML and XML) |
| 命令 | 描述 |
|---|---|
| 启用竞争检测并生成JUnit报告的测试执行命令 |
| 生成覆盖率报告(HTML和XML格式) |
5. Style Guide
5. 风格指南
-
Test FrameworkUse the standard Gopackage.
testing -
Include ImportsIncludeand
testingfor comparisons.github.com/google/go-cmp/cmp -
ParallelismUseto run tests in parallel.
t.Parallel() -
Test OrganizationConsolidate test cases for a single function into onefunction using table-driven testing.
TestXxx(t *testing.T)This approach:- Eliminates redundant test function definitions
- Simplifies maintenance by grouping related scenarios together
- Reduces code duplication in setup and teardown phases
- Makes it easier to add or modify test cases
-
AssertionsUsefor value comparisons and
cmp.Equalfor error checking.errors.Is
-
测试框架使用Go标准包。
testing -
导入依赖导入和
testing用于值比较。github.com/google/go-cmp/cmp -
并行执行使用实现测试并行运行。
t.Parallel() -
测试组织使用表格驱动测试,将单个函数的所有测试用例整合到一个函数中。
TestXxx(t *testing.T)该方法的优势:- 消除冗余的测试函数定义
- 通过分组相关场景简化维护
- 减少初始化和清理阶段的代码重复
- 便于添加或修改测试用例
-
断言方式使用进行值比较,使用
cmp.Equal进行错误检查。errors.Is
6. Template
6. 模板
Use these templates for new unit tests. Replace placeholders with actual values.
使用以下模板创建新单元测试。将占位符替换为实际值。
6.1. File Header Template
6.1. 文件头模板
go
// SPDX-License-Identifier: Apache-2.0
package <package>
import (
"errors"
"testing"
"github.com/google/go-cmp/cmp"
)go
// SPDX-License-Identifier: Apache-2.0
package <package>
import (
"errors"
"testing"
"github.com/google/go-cmp/cmp"
)6.2. Table-Driven Test Template
6.2. 表格驱动测试模板
go
func Test<FunctionName>(t *testing.T) {
t.Parallel()
// In-Got-Want
type in struct {
/* input fields */
}
type want struct {
/* expected output fields */
err error
}
// Table-Driven Testing
tests := []struct {
name string
in in
want want
}{
{
name: "case-description-1",
in: in{
/* input values */
},
want: want{
/* expected output */
err: nil,
},
},
{
name: "case-description-2",
in: in{
/* input values */
},
want: want{
/* expected output */
err: nil, // or specific error
},
},
// add more cases as needed
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Arrange
// additional setup as needed
// Act
got, err := <Function>(tt.in.<input>)
// Assert
if !errors.Is(err, tt.want.err) {
t.Errorf("<Function>() error = %v, want err %v", err, tt.want.err)
}
if !cmp.Equal(got, tt.want.<value>) {
t.Errorf("<Function>(%+v) = %v, want %v", tt.in, got, tt.want.<value>)
}
})
}
}go
func Test<FunctionName>(t *testing.T) {
t.Parallel()
// In-Got-Want
type in struct {
/* input fields */
}
type want struct {
/* expected output fields */
err error
}
// Table-Driven Testing
tests := []struct {
name string
in in
want want
}{
{
name: "case-description-1",
in: in{
/* input values */
},
want: want{
/* expected output */
err: nil,
},
},
{
name: "case-description-2",
in: in{
/* input values */
},
want: want{
/* expected output */
err: nil, // or specific error
},
},
// add more cases as needed
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Arrange
// additional setup as needed
// Act
got, err := <Function>(tt.in.<input>)
// Assert
if !errors.Is(err, tt.want.err) {
t.Errorf("<Function>() error = %v, want err %v", err, tt.want.err)
}
if !cmp.Equal(got, tt.want.<value>) {
t.Errorf("<Function>(%+v) = %v, want %v", tt.in, got, tt.want.<value>)
}
})
}
}6.3. Test Fixture Template
6.3. 测试夹具模板
go
// testFixture holds common test state and provides setup/teardown.
type testFixture struct {
t *testing.T
// Add common fields for test state
object *<Type>
}
// newTestFixture creates and initializes a test fixture.
func newTestFixture(t *testing.T) *testFixture {
t.Helper()
// Setup
return &testFixture{
t: t,
object: New<Type>(),
}
}
// teardown cleans up resources after test completion.
func (f *testFixture) teardown() {
f.t.Helper()
// Teardown
if f.object != nil {
f.object.Close()
}
}
func Test<FunctionName>WithFixture(t *testing.T) {
t.Parallel()
// Arrange
f := newTestFixture(t)
defer f.teardown()
input := <input_value>
// Act
got, err := f.object.<Function>(input)
// Assert
if err != nil {
t.Errorf("<Function>() unexpected error: %v", err)
}
if !cmp.Equal(got, <expected>) {
t.Errorf("<Function>() = %v, want %v", got, <expected>)
}
}go
// testFixture holds common test state and provides setup/teardown.
type testFixture struct {
t *testing.T
// Add common fields for test state
object *<Type>
}
// newTestFixture creates and initializes a test fixture.
func newTestFixture(t *testing.T) *testFixture {
t.Helper()
// Setup
return &testFixture{
t: t,
object: New<Type>(),
}
}
// teardown cleans up resources after test completion.
func (f *testFixture) teardown() {
f.t.Helper()
// Teardown
if f.object != nil {
f.object.Close()
}
}
func Test<FunctionName>WithFixture(t *testing.T) {
t.Parallel()
// Arrange
f := newTestFixture(t)
defer f.teardown()
input := <input_value>
// Act
got, err := f.object.<Function>(input)
// Assert
if err != nil {
t.Errorf("<Function>() unexpected error: %v", err)
}
if !cmp.Equal(got, <expected>) {
t.Errorf("<Function>() = %v, want %v", got, <expected>)
}
}6.4. Error Test Template
6.4. 错误测试模板
go
func Test<FunctionName>Error(t *testing.T) {
t.Parallel()
// In-Got-Want
type in struct {
/* invalid input fields */
}
type want struct {
err error
}
// Table-Driven Testing
tests := []struct {
name string
in in
want want
}{
{
name: "nil-input-returns-error",
in: in{
/* nil or invalid input */
},
want: want{
err: resource.Err<ErrorName>,
},
},
{
name: "invalid-value-returns-error",
in: in{
/* invalid value */
},
want: want{
err: resource.Err<ErrorName>,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Arrange
// setup if needed
// Act
_, err := <Function>(tt.in.<input>)
// Assert
if !errors.Is(err, tt.want.err) {
t.Errorf("<Function>() error = %v, want err %v", err, tt.want.err)
}
})
}
}go
func Test<FunctionName>Error(t *testing.T) {
t.Parallel()
// In-Got-Want
type in struct {
/* invalid input fields */
}
type want struct {
err error
}
// Table-Driven Testing
tests := []struct {
name string
in in
want want
}{
{
name: "nil-input-returns-error",
in: in{
/* nil or invalid input */
},
want: want{
err: resource.Err<ErrorName>,
},
},
{
name: "invalid-value-returns-error",
in: in{
/* invalid value */
},
want: want{
err: resource.Err<ErrorName>,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Arrange
// setup if needed
// Act
_, err := <Function>(tt.in.<input>)
// Assert
if !errors.Is(err, tt.want.err) {
t.Errorf("<Function>() error = %v, want err %v", err, tt.want.err)
}
})
}
}6.5. Boundary Value Test Template
6.5. 边界值测试模板
go
func Test<FunctionName>BoundaryValues(t *testing.T) {
t.Parallel()
// In-Got-Want
type in struct {
input <input_type>
}
type want struct {
value <output_type>
err error
}
// Table-Driven Testing
tests := []struct {
name string
in in
want want
}{
{
name: "minimum-value",
in: in{input: <MIN_VALUE>},
want: want{value: /* expected */, err: nil},
},
{
name: "maximum-value",
in: in{input: <MAX_VALUE>},
want: want{value: /* expected */, err: nil},
},
{
name: "zero-value",
in: in{input: 0},
want: want{value: /* expected */, err: nil},
},
{
name: "negative-value",
in: in{input: -1},
want: want{value: /* expected */, err: nil},
},
{
name: "overflow-value",
in: in{input: math.MaxFloat64},
want: want{value: 0, err: resource.ErrOverflow},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Arrange
// setup if needed
// Act
got, err := <Function>(tt.in.input)
// Assert
if !errors.Is(err, tt.want.err) {
t.Errorf("<Function>() error = %v, want err %v", err, tt.want.err)
}
if !cmp.Equal(got, tt.want.value) {
t.Errorf("<Function>(%v) = %v, want %v", tt.in.input, got, tt.want.value)
}
})
}
}go
func Test<FunctionName>BoundaryValues(t *testing.T) {
t.Parallel()
// In-Got-Want
type in struct {
input <input_type>
}
type want struct {
value <output_type>
err error
}
// Table-Driven Testing
tests := []struct {
name string
in in
want want
}{
{
name: "minimum-value",
in: in{input: <MIN_VALUE>},
want: want{value: /* expected */, err: nil},
},
{
name: "maximum-value",
in: in{input: <MAX_VALUE>},
want: want{value: /* expected */, err: nil},
},
{
name: "zero-value",
in: in{input: 0},
want: want{value: /* expected */, err: nil},
},
{
name: "negative-value",
in: in{input: -1},
want: want{value: /* expected */, err: nil},
},
{
name: "overflow-value",
in: in{input: math.MaxFloat64},
want: want{value: 0, err: resource.ErrOverflow},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Arrange
// setup if needed
// Act
got, err := <Function>(tt.in.input)
// Assert
if !errors.Is(err, tt.want.err) {
t.Errorf("<Function>() error = %v, want err %v", err, tt.want.err)
}
if !cmp.Equal(got, tt.want.value) {
t.Errorf("<Function>(%v) = %v, want %v", tt.in.input, got, tt.want.value)
}
})
}
}6.6. Data-Driven Test Template (JSON)
6.6. 数据驱动测试模板(JSON)
go
import (
"encoding/json"
"os"
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
)
// testCase represents a single test case loaded from JSON.
type testCase struct {
Name string `json:"name"`
In struct {
Input <input_type> `json:"input"`
} `json:"in"`
Want struct {
Expected <output_type> `json:"expected"`
} `json:"want"`
}
// testData represents the JSON test data structure.
type testData struct {
Tests []testCase `json:"tests"`
}
func Test<FunctionName>DataDriven(t *testing.T) {
t.Parallel()
// Load test data from JSON file
testdataPath := filepath.Join("testdata", "<function>_test.json")
data, err := os.ReadFile(testdataPath)
if err != nil {
t.Fatalf("failed to read test data: %v", err)
}
var td testData
if err := json.Unmarshal(data, &td); err != nil {
t.Fatalf("failed to parse test data: %v", err)
}
for _, tc := range td.Tests {
t.Run(tc.Name, func(t *testing.T) {
// Arrange
input := tc.In.Input
expected := tc.Want.Expected
// Act
got, err := <Function>(input)
// Assert
if err != nil {
t.Errorf("<Function>() unexpected error: %v", err)
}
if !cmp.Equal(got, expected) {
t.Errorf("<Function>(%v) = %v, want %v", input, got, expected)
}
})
}
}-
tests/data/<function>_test.jsonJSON file containing test cases.json{ "tests": [ { "name": "case-description-1", "in": { "input": <value> }, "want": { "expected": <value> } }, { "name": "case-description-2", "in": { "input": <value> }, "want": { "expected": <value> } } ] }
go
import (
"encoding/json"
"os"
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
)
// testCase represents a single test case loaded from JSON.
type testCase struct {
Name string `json:"name"`
In struct {
Input <input_type> `json:"input"`
} `json:"in"`
Want struct {
Expected <output_type> `json:"expected"`
} `json:"want"`
}
// testData represents the JSON test data structure.
type testData struct {
Tests []testCase `json:"tests"`
}
func Test<FunctionName>DataDriven(t *testing.T) {
t.Parallel()
// Load test data from JSON file
testdataPath := filepath.Join("testdata", "<function>_test.json")
data, err := os.ReadFile(testdataPath)
if err != nil {
t.Fatalf("failed to read test data: %v", err)
}
var td testData
if err := json.Unmarshal(data, &td); err != nil {
t.Fatalf("failed to parse test data: %v", err)
}
for _, tc := range td.Tests {
t.Run(tc.Name, func(t *testing.T) {
// Arrange
input := tc.In.Input
expected := tc.Want.Expected
// Act
got, err := <Function>(input)
// Assert
if err != nil {
t.Errorf("<Function>() unexpected error: %v", err)
}
if !cmp.Equal(got, expected) {
t.Errorf("<Function>(%v) = %v, want %v", input, got, expected)
}
})
}
}-
tests/data/<function>_test.json包含测试用例的JSON文件。json{ "tests": [ { "name": "case-description-1", "in": { "input": <value> }, "want": { "expected": <value> } }, { "name": "case-description-2", "in": { "input": <value> }, "want": { "expected": <value> } } ] }