Golang中测试辅助函数的最佳存放位置探讨

Golang中测试辅助函数的最佳存放位置探讨 我有多个测试都在使用相同的函数,主要是模拟和桩函数功能。这些东西应该放在哪里?有没有惯用的位置来存放这些函数?

6 回复

谢谢!

更多关于Golang中测试辅助函数的最佳存放位置探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我不确定Go语言测试是否有明确的规范要求。

我认为应该为单元测试、集成测试和系统测试分别创建不同的文件。

请查看以下链接

谢谢!

我想我已经大致理解了。
假设我有两个测试文件:

main_test.go
user_test.go

这两个文件都在使用一个仅用于测试目的且被多个测试需要的函数。

假设是一个用于创建某种模拟用户的函数。
我应该把它放在哪里?

// 测试相关的辅助函数可以放在单独的测试工具文件中

嗨 mendlock,你应该为测试函数创建一个特定名称的文件。例如,如果你有一个名为"newDeck"的函数,你应该在另一个文件中创建"TestNewDeck"函数。

这是Golang测试文档

我还附上了一个示例

你也可以运行"Go test"来执行这些测试并生成结果报告。

致意 笑脸

你可以拥有多个 _test.go 文件。如果在文件夹内运行 “go test” 命令,它会找到所有测试。例如我有三个文件 main.go、main_test.go 和 mock_test.go,然后运行 go test -v(-v 表示详细模式),它会找到 main_test.go 中的测试和 mock_test.go 中的结构体,并输出如下内容:

$ go test -v
=== RUN   TestUsername
--- PASS: TestUsername (0.00s)
PASS
ok      _/Users/xxx/projects/zzz       0.008s

mock_test.go

package main

type MockUser struct {
}

func (u MockUser) Name() string {
	return "Peter"
}

main_test.go

package main

import "testing"

func TestUsername(t *testing.T) {
	u := MockUser{}
	name := u.Name()

	if name != "Peter" {
		t.Errorf("wanted Peter got %s", name)
	}
}

如果你想要的话,可以在更多 _test.go 文件中使用 Mockuser。

在Go项目中,测试辅助函数的最佳存放位置通常遵循以下惯例:

1. 在同包内的测试文件中

最常见的做法是将共享的测试辅助函数放在同包的 *_test.go 文件中:

// user_service.go
package user

type UserService struct {
    repo UserRepository
}

func (s *UserService) GetUser(id int) (*User, error) {
    return s.repo.FindByID(id)
}
// user_service_test.go
package user

import (
    "testing"
    "github.com/stretchr/testify/assert"
)

// 测试辅助函数 - 模拟用户
func mockUser() *User {
    return &User{
        ID:    1,
        Name:  "测试用户",
        Email: "test@example.com",
    }
}

// 桩函数 - 模拟仓库
type mockRepository struct{}

func (m *mockRepository) FindByID(id int) (*User, error) {
    return mockUser(), nil
}

func TestUserService_GetUser(t *testing.T) {
    service := &UserService{repo: &mockRepository{}}
    user, err := service.GetUser(1)
    
    assert.NoError(t, err)
    assert.Equal(t, "测试用户", user.Name)
}

2. 使用 internal/testutils 包

对于跨多个包共享的测试辅助函数,可以创建 internal/testutils 包:

project/
├── internal/
│   └── testutils/
│       ├── helpers.go
│       └── mocks.go
├── user/
│   ├── user_service.go
│   └── user_service_test.go
└── order/
    ├── order_service.go
    └── order_service_test.go
// internal/testutils/helpers.go
package testutils

import "time"

// 跨包共享的测试辅助函数
func CreateTestTime() time.Time {
    return time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
}

func Pointer[T any](v T) *T {
    return &v
}

3. 使用测试辅助文件约定

创建专门的测试辅助文件,通常命名为 helpers_test.gotest_helpers.go

// helpers_test.go
package user

import "testing"

var (
    TestUserID   = 1
    TestUserName = "测试用户"
)

// 设置测试环境
func setupTest(t *testing.T) (*UserService, func()) {
    repo := &mockRepository{}
    service := &UserService{repo: repo}
    
    // 返回清理函数
    cleanup := func() {
        // 清理资源
    }
    
    return service, cleanup
}

// 验证用户数据的辅助函数
func verifyUser(t *testing.T, user *User, expectedID int, expectedName string) {
    if user.ID != expectedID {
        t.Errorf("期望用户ID %d, 得到 %d", expectedID, user.ID)
    }
    if user.Name != expectedName {
        t.Errorf("期望用户名 %s, 得到 %s", expectedName, user.Name)
    }
}

4. 表格驱动测试中的辅助函数

对于表格驱动测试,可以将测试用例定义放在辅助函数中:

// user_service_test.go
package user

import "testing"

type getUserTestCase struct {
    name          string
    userID        int
    expectedUser  *User
    expectedError error
    setupMock     func(repo *mockRepository)
}

func getTestCases() []getUserTestCase {
    return []getUserTestCase{
        {
            name:   "成功获取用户",
            userID: 1,
            expectedUser: &User{
                ID:    1,
                Name:  "用户1",
                Email: "user1@example.com",
            },
            setupMock: func(repo *mockRepository) {
                // 设置模拟行为
            },
        },
        {
            name:          "用户不存在",
            userID:        999,
            expectedUser:  nil,
            expectedError: ErrUserNotFound,
            setupMock: func(repo *mockRepository) {
                // 设置返回错误的模拟行为
            },
        },
    }
}

func TestUserService_GetUser_TableDriven(t *testing.T) {
    for _, tc := range getTestCases() {
        t.Run(tc.name, func(t *testing.T) {
            repo := &mockRepository{}
            tc.setupMock(repo)
            service := &UserService{repo: repo}
            
            user, err := service.GetUser(tc.userID)
            
            // 验证结果
        })
    }
}

这些方法遵循Go社区的惯例,保持代码组织清晰且易于维护。选择哪种方式取决于辅助函数的共享范围和项目的具体结构需求。

回到顶部