go-unit-tests

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Go Unit Tests

Go单元测试

Generate comprehensive Go unit tests following testify patterns and the Arrange-Act-Assert methodology.
按照testify模式和Arrange-Act-Assert方法论生成全面的Go单元测试。

Planning Phase

规划阶段

Before writing tests, identify:
  1. Test Structure: Determine if test suite (for structs with dependencies) or individual test functions (for standalone functions) should be used
  2. Dependencies: Identify dependencies or side effects requiring mocks or stubs
  3. Test Cases: Define scenarios covering happy paths, edge cases, and error conditions
  4. Naming: Number each test case clearly (e.g.,
    TestFunction_ValidInput_ReturnsExpectedResult
    ,
    TestFunction_EmptyInput_ReturnsError
    )
Show the code without explanations during planning.
编写测试前,需明确:
  1. 测试结构:确定应使用测试套件(适用于带有依赖的结构体)还是独立测试函数(适用于独立函数)
  2. 依赖项:识别需要mocks或stubs的依赖项或副作用
  3. 测试用例:定义涵盖正常路径、边缘情况和错误场景的测试场景
  4. 命名规范:为每个测试用例清晰命名(例如:
    TestFunction_ValidInput_ReturnsExpectedResult
    TestFunction_EmptyInput_ReturnsError
规划阶段仅展示代码,不附带解释。

Implementation Patterns

实现模式

Pattern 1: Test Suites for Structs with Dependencies

模式1:带依赖结构体的测试套件

Use
suite.Suite
from testify for structs with dependencies.
Key Rules:
  • Create suite struct with
    sut
    (System Under Test) field
  • Implement
    SetupTest
    method to initialize sut and dependencies
  • Use constructor (typically
    NewTypeName
    ) to create instances
  • Always use
    _test
    suffix for package name
  • Use
    suite
    methods for assertions (e.g.,
    suite.Equal(v, 10)
    )
  • Use
    suite.Require()
    for error assertions (e.g.,
    suite.Require().ErrorIs
    ,
    suite.Require().Error
    )
  • Never use
    .AssertExpectations(s.T())
Example:
go
package mypackage_test

import (
	"testing"

	"github.com/stretchr/testify/suite"
)

type MyStructTestSuite struct {
	suite.Suite
	sut *mypackage.MyStruct
}

func (s *MyStructTestSuite) SetupTest() {
	// Initialize sut and dependencies
	s.sut = mypackage.New()
}

func TestMyStructSuite(t *testing.T) {
	suite.Run(t, new(MyStructTestSuite))
}

func (s *MyStructTestSuite) TestSomeMethod() {
	// Arrange
	input := "test input"
	expected := "expected output"

	// Act
	result := s.sut.SomeMethod(input)

	// Assert
	s.Equal(expected, result)
}

func (s *MyStructTestSuite) TestSomeMethod_WithError() {
	// Arrange
	invalidInput := ""

	// Act
	result, err := s.sut.SomeMethodWithError(invalidInput)

	// Assert
	s.Require().Error(err)
	s.Empty(result)
}
With Mocks:
go
package mypackage_test

import (
	"context"
	"testing"

	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/suite"

	"github.com/example/project/test/mocks"
)

type UserServiceTestSuite struct {
	suite.Suite
	sut              *mypackage.UserService
	userRepoMock     *mocks.MockUserRepository
	tokenServiceMock *mocks.MockTokenService
}

func (s *UserServiceTestSuite) SetupTest() {
	// Initialize mocks
	s.userRepoMock = mocks.NewMockUserRepository(s.T())
	s.tokenServiceMock = mocks.NewMockTokenService(s.T())

	// Initialize sut with mocked dependencies
	s.sut = mypackage.NewUserService(
		s.userRepoMock,
		s.tokenServiceMock,
	)
}

func TestUserServiceSuite(t *testing.T) {
	suite.Run(t, new(UserServiceTestSuite))
}

func (s *UserServiceTestSuite) TestCreateUser_ValidInput_CreatesUser() {
	// Arrange
	ctx := context.Background()
	user := &mypackage.User{
		Email: "test@example.com",
		Name:  "Test User",
	}

	s.userRepoMock.On("Create", mock.Anything, user).Return(nil)

	// Act
	err := s.sut.CreateUser(ctx, user)

	// Assert
	s.Require().NoError(err)
}

func (s *UserServiceTestSuite) TestCreateUser_RepositoryError_ReturnsError() {
	// Arrange
	ctx := context.Background()
	user := &mypackage.User{
		Email: "test@example.com",
		Name:  "Test User",
	}
	expectedError := errors.New("repository error")

	s.userRepoMock.On("Create", mock.Anything, user).Return(expectedError)

	// Act
	err := s.sut.CreateUser(ctx, user)

	// Assert
	s.Require().ErrorIs(err, expectedError)
}

func (s *UserServiceTestSuite) TestGenerateToken_ValidUser_ReturnsToken() {
	// Arrange
	ctx := context.Background()
	userID := "user-123"
	expectedToken := "token-abc"

	s.tokenServiceMock.On(
		"Generate",
		mock.Anything,
		userID,
	).Return(expectedToken, nil)

	// Act
	token, err := s.sut.GenerateToken(ctx, userID)

	// Assert
	s.Require().NoError(err)
	s.Equal(expectedToken, token)
}
Mock Rules:
  • Always pass
    mock.Anything
    for context parameters
  • Mock naming follows pattern
    MockType
    (e.g.,
    MockUserRepository
    ,
    MockTokenService
    )
  • Import mocks with aliases:
    user_repository_mocks "github.com/project/internal/domain/repository/mocks"
使用testify的
suite.Suite
为带有依赖的结构体编写测试。
核心规则:
  • 创建包含
    sut
    (被测系统)字段的套件结构体
  • 实现
    SetupTest
    方法以初始化sut和依赖项
  • 使用构造函数(通常为
    NewTypeName
    )创建实例
  • 包名必须以
    _test
    为后缀
  • 使用
    suite
    方法进行断言(例如:
    suite.Equal(v, 10)
  • 使用
    suite.Require()
    进行错误断言(例如:
    suite.Require().ErrorIs
    suite.Require().Error
  • 禁止使用
    .AssertExpectations(s.T())
示例:
go
package mypackage_test

import (
	"testing"

	"github.com/stretchr/testify/suite"
)

type MyStructTestSuite struct {
	suite.Suite
	sut *mypackage.MyStruct
}

func (s *MyStructTestSuite) SetupTest() {
	// Initialize sut and dependencies
	s.sut = mypackage.New()
}

func TestMyStructSuite(t *testing.T) {
	suite.Run(t, new(MyStructTestSuite))
}

func (s *MyStructTestSuite) TestSomeMethod() {
	// Arrange
	input := "test input"
	expected := "expected output"

	// Act
	result := s.sut.SomeMethod(input)

	// Assert
	s.Equal(expected, result)
}

func (s *MyStructTestSuite) TestSomeMethod_WithError() {
	// Arrange
	invalidInput := ""

	// Act
	result, err := s.sut.SomeMethodWithError(invalidInput)

	// Assert
	s.Require().Error(err)
	s.Empty(result)
}
含Mocks的示例:
go
package mypackage_test

import (
	"context"
	"testing"

	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/suite"

	"github.com/example/project/test/mocks"
)

type UserServiceTestSuite struct {
	suite.Suite
	sut              *mypackage.UserService
	userRepoMock     *mocks.MockUserRepository
	tokenServiceMock *mocks.MockTokenService
}

func (s *UserServiceTestSuite) SetupTest() {
	// Initialize mocks
	s.userRepoMock = mocks.NewMockUserRepository(s.T())
	s.tokenServiceMock = mocks.NewMockTokenService(s.T())

	// Initialize sut with mocked dependencies
	s.sut = mypackage.NewUserService(
		s.userRepoMock,
		s.tokenServiceMock,
	)
}

func TestUserServiceSuite(t *testing.T) {
	suite.Run(t, new(UserServiceTestSuite))
}

func (s *UserServiceTestSuite) TestCreateUser_ValidInput_CreatesUser() {
	// Arrange
	ctx := context.Background()
	user := &mypackage.User{
		Email: "test@example.com",
		Name:  "Test User",
	}

	s.userRepoMock.On("Create", mock.Anything, user).Return(nil)

	// Act
	err := s.sut.CreateUser(ctx, user)

	// Assert
	s.Require().NoError(err)
}

func (s *UserServiceTestSuite) TestCreateUser_RepositoryError_ReturnsError() {
	// Arrange
	ctx := context.Background()
	user := &mypackage.User{
		Email: "test@example.com",
		Name:  "Test User",
	}
	expectedError := errors.New("repository error")

	s.userRepoMock.On("Create", mock.Anything, user).Return(expectedError)

	// Act
	err := s.sut.CreateUser(ctx, user)

	// Assert
	s.Require().ErrorIs(err, expectedError)
}

func (s *UserServiceTestSuite) TestGenerateToken_ValidUser_ReturnsToken() {
	// Arrange
	ctx := context.Background()
	userID := "user-123"
	expectedToken := "token-abc"

	s.tokenServiceMock.On(
		"Generate",
		mock.Anything,
		userID,
	).Return(expectedToken, nil)

	// Act
	token, err := s.sut.GenerateToken(ctx, userID)

	// Assert
	s.Require().NoError(err)
	s.Equal(expectedToken, token)
}
Mock规则:
  • 上下文参数始终传递
    mock.Anything
  • Mock命名遵循
    MockType
    格式(例如:
    MockUserRepository
    MockTokenService
  • 使用别名导入mocks:
    user_repository_mocks "github.com/project/internal/domain/repository/mocks"

Pattern 2: Tests for Standalone Functions

模式2:独立函数的测试

Use individual test functions with subtests for functions without instances.
Key Rules:
  • Create test functions using
    func TestXxx(t *testing.T)
  • Use
    t.Run
    for subtests covering different scenarios
  • Use
    require
    for error assertions (e.g.,
    require.ErrorIs
    ,
    require.Error
    )
Example:
go
package mypackage_test

import (
	"testing"

	"github.com/stretchr/testify/require"
)

func TestSomeFunction(t *testing.T) {
	t.Run("valid input returns expected result", func(t *testing.T) {
		// Arrange
		input := "test input"
		expected := "expected output"

		// Act
		result := SomeFunction(input)

		// Assert
		require.Equal(t, expected, result)
	})

	t.Run("empty input returns error", func(t *testing.T) {
		// Arrange
		input := ""

		// Act
		result, err := SomeFunctionWithError(input)

		// Assert
		require.Error(t, err)
		require.Empty(t, result)
	})

	t.Run("nil input returns error", func(t *testing.T) {
		// Arrange
		var input *string

		// Act
		result, err := SomeFunctionWithPointer(input)

		// Assert
		require.ErrorIs(t, err, ErrNilInput)
		require.Empty(t, result)
	})
}
为无实例的函数创建独立测试函数并使用子测试。
核心规则:
  • 使用
    func TestXxx(t *testing.T)
    创建测试函数
  • 使用
    t.Run
    创建涵盖不同场景的子测试
  • 使用
    require
    进行错误断言(例如:
    require.ErrorIs
    require.Error
示例:
go
package mypackage_test

import (
	"testing"

	"github.com/stretchr/testify/require"
)

func TestSomeFunction(t *testing.T) {
	t.Run("valid input returns expected result", func(t *testing.T) {
		// Arrange
		input := "test input"
		expected := "expected output"

		// Act
		result := SomeFunction(input)

		// Assert
		require.Equal(t, expected, result)
	})

	t.Run("empty input returns error", func(t *testing.T) {
		// Arrange
		input := ""

		// Act
		result, err := SomeFunctionWithError(input)

		// Assert
		require.Error(t, err)
		require.Empty(t, result)
	})

	t.Run("nil input returns error", func(t *testing.T) {
		// Arrange
		var input *string

		// Act
		result, err := SomeFunctionWithPointer(input)

		// Assert
		require.ErrorIs(t, err, ErrNilInput)
		require.Empty(t, result)
	})
}

Test Structure Requirements

测试结构要求

Arrange-Act-Assert Pattern

Arrange-Act-Assert模式

Every test must follow AAA pattern with explicit comments:
go
// Arrange
// Act
// Assert
每个测试必须遵循AAA模式,并添加明确注释:
go
// Arrange
// Act
// Assert

Code Style

代码风格

  • Never use inline struct construction; always create variable first
  • Maximum 120 characters per line
  • Test names must clearly indicate what is being tested
  • Add comments for complex test setups or assertions
  • 禁止使用内联结构体构造,必须先创建变量
  • 每行最多120个字符
  • 测试名称必须清晰表明测试内容
  • 复杂测试设置或断言需添加注释

Test Coverage

测试覆盖率

  • Include happy path scenarios
  • Include edge cases
  • Include error handling
  • Aim for minimum test scenarios possible while maintaining at least 80% coverage
  • 包含正常路径场景
  • 包含边缘情况
  • 包含错误处理场景
  • 在保持至少80%覆盖率的前提下,使用最少的测试场景

Completion

完成标识

When tests are complete, respond with: Tests Done, Oh Yeah!
测试编写完成后,回复:Tests Done, Oh Yeah!