go-integration-tests
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGo Integration Tests
Go集成测试
Generate comprehensive Go integration tests using testify suite patterns with real database and infrastructure dependencies.
使用testify suite模式结合真实数据库与基础设施依赖项,生成全面的Go集成测试。
Planning Phase
规划阶段
Before writing tests, identify:
- Test Location: Tests go in mirroring the source path from
test/integration/internal/- Example: →
internal/modules/identity/usecase/user/user_register_usecase.gotest/integration/modules/identity/usecase/user/user_register_usecase_test.go
- Example:
- Dependencies: Identify which real dependencies (database, redis) vs mocked dependencies (email, external APIs)
- Test Cases: Define scenarios covering happy paths, edge cases, and error conditions
- Naming: Number each test case clearly (e.g., ,
TestExecute_ValidInput_ReturnsUser)TestExecute_DuplicateEmail_ReturnsError
Show the code without explanations during planning.
编写测试前,需明确:
- 测试文件位置:测试文件放在目录下,与
test/integration/中的源码路径一一对应internal/- 示例:→
internal/modules/identity/usecase/user/user_register_usecase.gotest/integration/modules/identity/usecase/user/user_register_usecase_test.go
- 示例:
- 依赖项区分:明确哪些是真实依赖项(数据库、Redis),哪些是模拟依赖项(邮件、外部API)
- 测试用例定义:覆盖正常流程、边界情况及错误场景的测试场景
- 命名规范:为每个测试用例清晰编号(例如:、
TestExecute_ValidInput_ReturnsUser)TestExecute_DuplicateEmail_ReturnsError
规划阶段仅展示代码,无需附加说明。
Implementation Patterns
实现模式
Pattern: Integration Test Suite
模式:集成测试套件
Use from testify with itestkit for containerized infrastructure.
suite.SuiteKey Rules:
- Create suite struct with (System Under Test),
sut(ITestKit), andkitfieldsdb - Implement to start containers and run migrations (runs once)
SetupSuite - Implement to stop containers (runs once)
TearDownSuite - Implement to truncate tables and initialize sut (runs before each test)
SetupTest - Use build tag at the top of the file
//go:build integration - Always use suffix for package name
_test - Use methods for assertions (e.g.,
suite)suite.Equal(v, 10) - Use for error assertions (e.g.,
suite.Require(),suite.Require().ErrorIs)suite.Require().Error - Never use
.AssertExpectations(s.T())
Example:
go
//go:build integration
package user_test
import (
"context"
"testing"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
"github.com/cristiano-pacheco/bricks/pkg/itestkit"
"github.com/cristiano-pacheco/bricks/pkg/validator"
"github.com/cristiano-pacheco/pingo/internal/modules/identity/repository"
"github.com/cristiano-pacheco/pingo/internal/modules/identity/usecase/user"
"github.com/cristiano-pacheco/pingo/internal/shared/config"
"github.com/cristiano-pacheco/pingo/internal/shared/database"
"github.com/cristiano-pacheco/pingo/test/mocks"
)
func TestMain(m *testing.M) {
itestkit.TestMain(m)
}
type UserRegisterUseCaseTestSuite struct {
suite.Suite
kit *itestkit.ITestKit
db *database.PingoDB
sut *user.UserRegisterUseCase
emailSender *mocks.MockEmailSender
cfg config.Config
}
func TestUserRegisterUseCaseSuite(t *testing.T) {
suite.Run(t, new(UserRegisterUseCaseTestSuite))
}
func (s *UserRegisterUseCaseTestSuite) SetupSuite() {
s.kit = itestkit.New(itestkit.Config{
PostgresImage: "postgres:16-alpine",
RedisImage: "redis:7-alpine",
MigrationsPath: "file://migrations",
Database: "pingo_test",
User: "pingo_test",
Password: "pingo_test",
})
err := s.kit.StartPostgres()
s.Require().NoError(err)
err = s.kit.RunMigrations()
s.Require().NoError(err)
s.db = &database.PingoDB{DB: s.kit.DB()}
}
func (s *UserRegisterUseCaseTestSuite) TearDownSuite() {
if s.kit != nil {
s.kit.StopPostgres()
}
}
func (s *UserRegisterUseCaseTestSuite) SetupTest() {
s.kit.TruncateTables(s.T())
s.emailSender = mocks.NewMockEmailSender(s.T())
s.cfg = s.createTestConfig()
s.sut = s.createTestUseCase()
}
func (s *UserRegisterUseCaseTestSuite) createTestConfig() config.Config {
return config.Config{
App: config.AppConfig{
BaseURL: "http://test.example.com",
},
}
}
func (s *UserRegisterUseCaseTestSuite) createTestUseCase() *user.UserRegisterUseCase {
log := new(mocks.MockLogger)
v, err := validator.New()
s.Require().NoError(err)
userRepo := repository.NewUserRepository(s.db)
return user.NewUserRegisterUseCase(
userRepo,
s.emailSender,
v,
s.cfg,
log,
)
}
func (s *UserRegisterUseCaseTestSuite) TestExecute_ValidInput_ReturnsUser() {
// Arrange
ctx := context.Background()
input := user.UserRegisterInput{
Email: "test@example.com",
Password: "Password123!",
FirstName: "John",
LastName: "Doe",
}
// Act
output, err := s.sut.Execute(ctx, input)
// Assert
s.Require().NoError(err)
s.NotZero(output.ID)
s.Equal(input.Email, output.Email)
var savedUser model.UserModel
err = s.db.DB.Where("id = ?", output.ID).First(&savedUser).Error
s.Require().NoError(err)
s.Equal(input.Email, savedUser.Email)
}
func (s *UserRegisterUseCaseTestSuite) TestExecute_DuplicateEmail_ReturnsError() {
// Arrange
ctx := context.Background()
input := user.UserRegisterInput{
Email: "test@example.com",
Password: "Password123!",
FirstName: "John",
LastName: "Doe",
}
// Act - First registration
_, err := s.sut.Execute(ctx, input)
s.Require().NoError(err)
// Act - Second registration with same email
_, err = s.sut.Execute(ctx, input)
// Assert
s.Require().Error(err)
s.ErrorIs(err, errs.ErrDuplicateEmail)
}结合itestkit,使用testify的来实现容器化基础设施的集成测试。
suite.Suite核心规则:
- 创建包含(被测系统)、
sut(ITestKit)和kit字段的套件结构体db - 实现方法启动容器并执行迁移(仅运行一次)
SetupSuite - 实现方法停止容器(仅运行一次)
TearDownSuite - 实现方法清空表数据并初始化sut(每个测试前运行)
SetupTest - 在文件顶部添加构建标签
//go:build integration - 包名必须以结尾
_test - 使用方法进行断言(例如:
suite)suite.Equal(v, 10) - 使用进行错误断言(例如:
suite.Require()、suite.Require().ErrorIs)suite.Require().Error - 禁止使用
.AssertExpectations(s.T())
示例:
go
//go:build integration
package user_test
import (
"context"
"testing"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
"github.com/cristiano-pacheco/bricks/pkg/itestkit"
"github.com/cristiano-pacheco/bricks/pkg/validator"
"github.com/cristiano-pacheco/pingo/internal/modules/identity/repository"
"github.com/cristiano-pacheco/pingo/internal/modules/identity/usecase/user"
"github.com/cristiano-pacheco/pingo/internal/shared/config"
"github.com/cristiano-pacheco/pingo/internal/shared/database"
"github.com/cristiano-pacheco/pingo/test/mocks"
)
func TestMain(m *testing.M) {
itestkit.TestMain(m)
}
type UserRegisterUseCaseTestSuite struct {
suite.Suite
kit *itestkit.ITestKit
db *database.PingoDB
sut *user.UserRegisterUseCase
emailSender *mocks.MockEmailSender
cfg config.Config
}
func TestUserRegisterUseCaseSuite(t *testing.T) {
suite.Run(t, new(UserRegisterUseCaseTestSuite))
}
func (s *UserRegisterUseCaseTestSuite) SetupSuite() {
s.kit = itestkit.New(itestkit.Config{
PostgresImage: "postgres:16-alpine",
RedisImage: "redis:7-alpine",
MigrationsPath: "file://migrations",
Database: "pingo_test",
User: "pingo_test",
Password: "pingo_test",
})
err := s.kit.StartPostgres()
s.Require().NoError(err)
err = s.kit.RunMigrations()
s.Require().NoError(err)
s.db = &database.PingoDB{DB: s.kit.DB()}
}
func (s *UserRegisterUseCaseTestSuite) TearDownSuite() {
if s.kit != nil {
s.kit.StopPostgres()
}
}
func (s *UserRegisterUseCaseTestSuite) SetupTest() {
s.kit.TruncateTables(s.T())
s.emailSender = mocks.NewMockEmailSender(s.T())
s.cfg = s.createTestConfig()
s.sut = s.createTestUseCase()
}
func (s *UserRegisterUseCaseTestSuite) createTestConfig() config.Config {
return config.Config{
App: config.AppConfig{
BaseURL: "http://test.example.com",
},
}
}
func (s *UserRegisterUseCaseTestSuite) createTestUseCase() *user.UserRegisterUseCase {
log := new(mocks.MockLogger)
v, err := validator.New()
s.Require().NoError(err)
userRepo := repository.NewUserRepository(s.db)
return user.NewUserRegisterUseCase(
userRepo,
s.emailSender,
v,
s.cfg,
log,
)
}
func (s *UserRegisterUseCaseTestSuite) TestExecute_ValidInput_ReturnsUser() {
// Arrange
ctx := context.Background()
input := user.UserRegisterInput{
Email: "test@example.com",
Password: "Password123!",
FirstName: "John",
LastName: "Doe",
}
// Act
output, err := s.sut.Execute(ctx, input)
// Assert
s.Require().NoError(err)
s.NotZero(output.ID)
s.Equal(input.Email, output.Email)
var savedUser model.UserModel
err = s.db.DB.Where("id = ?", output.ID).First(&savedUser).Error
s.Require().NoError(err)
s.Equal(input.Email, savedUser.Email)
}
func (s *UserRegisterUseCaseTestSuite) TestExecute_DuplicateEmail_ReturnsError() {
// Arrange
ctx := context.Background()
input := user.UserRegisterInput{
Email: "test@example.com",
Password: "Password123!",
FirstName: "John",
LastName: "Doe",
}
// Act - First registration
_, err := s.sut.Execute(ctx, input)
s.Require().NoError(err)
// Act - Second registration with same email
_, err = s.sut.Execute(ctx, input)
// Assert
s.Require().Error(err)
s.ErrorIs(err, errs.ErrDuplicateEmail)
}Mock Rules for Integration Tests
集成测试的Mock规则
- Mock external services (email, SMS, external APIs) that cannot run locally
- Use real database connections (via itestkit)
- Use real Redis connections when testing cache (via itestkit)
- Mock metrics and logger dependencies with to allow optional calls
.Maybe() - Always pass for context parameters
mock.Anything
Example with Mocks:
go
func (s *UserRegisterUseCaseTestSuite) SetupTest() {
s.kit.TruncateTables(s.T())
s.emailSender = mocks.NewMockEmailSender(s.T())
s.tokenGenerator = mocks.NewMockTokenGenerator(s.T())
// Setup optional mock expectations
s.emailSender.On("Send", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(nil).Maybe()
s.tokenGenerator.On("GenerateToken").Return("test-token", nil).Maybe()
s.sut = s.createTestUseCase()
}- 对无法在本地运行的外部服务(邮件、短信、外部API)进行Mock
- 通过itestkit使用真实数据库连接
- 测试缓存时通过itestkit使用真实Redis连接
- 对指标和日志依赖项使用进行Mock,允许可选调用
.Maybe() - 上下文参数始终传递
mock.Anything
Mock示例:
go
func (s *UserRegisterUseCaseTestSuite) SetupTest() {
s.kit.TruncateTables(s.T())
s.emailSender = mocks.NewMockEmailSender(s.T())
s.tokenGenerator = mocks.NewMockTokenGenerator(s.T())
// Setup optional mock expectations
s.emailSender.On("Send", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(nil).Maybe()
s.tokenGenerator.On("GenerateToken").Return("test-token", nil).Maybe()
s.sut = s.createTestUseCase()
}Test Structure Requirements
测试结构要求
(CRITICAL) Arrange-Act-Assert Pattern
(关键)Arrange-Act-Assert(AAA)模式
Every test must follow AAA pattern with explicit comments:
go
// Arrange
// Act
// Assert每个测试必须遵循AAA模式,并添加明确的注释:
go
// Arrange
// Act
// AssertCode 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
- Verify database state after operations
- Test data persistence and retrieval
- 包含正常流程场景
- 包含边界情况
- 包含错误处理场景
- 验证操作后的数据库状态
- 测试数据的持久化与检索
Test File Location
测试文件位置
Integration tests mirror the source structure under :
test/integration/| Source File | Integration Test File |
|---|---|
| |
| |
集成测试文件在目录下镜像源码结构:
test/integration/| 源码文件 | 集成测试文件 |
|---|---|
| |
| |
Running Integration Tests
运行集成测试
bash
undefinedbash
undefinedRun all integration tests
Run all integration tests
make test-integration
undefinedmake test-integration
undefinedCompletion
完成标识
When tests are complete, respond with: Integration Tests Done, Oh Yeah!
测试完成后,回复:Integration Tests Done, Oh Yeah!