testkit

package
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 13, 2026 License: MIT Imports: 20 Imported by: 0

README

Genesis 测试指南

本文档规定了 Genesis 项目的测试策略和规范,旨在确保代码质量、稳定性和可维护性。

1. 核心原则

  1. 轻量快速:单元测试应能在毫秒级完成,go test ./... 应能快速反馈。
  2. 高覆盖率:核心业务逻辑覆盖率 > 80%,工具包 > 90%。
  3. 面向接口:依赖接口而非具体实现,便于 Mock 和测试。
  4. 真实集成:集成测试使用 testcontainers 自动管理外部依赖,无需手动启动服务。
  5. 并发安全:必须启用 -race 进行竞态检测。

2. 分层测试策略

L1: 单元测试 (Unit Tests)
  • 目标:验证函数、方法、核心逻辑的正确性。
  • 范围:纯函数、业务逻辑、工具类。
  • 工具:标准库 testingtestify/assertgomock
  • 要求
    • 不依赖外部 I/O(数据库、网络)。
    • 使用表格驱动测试 (Table-driven tests)。
    • Mock 所有外部依赖。
L2: 集成测试 (Integration Tests)
  • 目标:验证组件与外部服务(Redis, MySQL, Etcd 等)的交互。
  • 范围:Connector, Repository, Cache 实现等。
  • 工具testkit(基于 testcontainers)。
  • 要求
    • 使用 testkit 自动启动和管理测试容器。
    • 测试环境应与生产环境尽可能一致(版本、配置)。
    • 测试后自动清理资源 (通过 t.Cleanup)。
L3: 端到端测试 (E2E Tests)
  • 目标:验证完整业务链路。
  • 范围:跨多个组件或服务的完整流程。
  • 方式:启动完整环境,模拟用户请求。

3. 使用 testkit 辅助测试

testkit 是 Genesis 提供的测试辅助包,位于 testkit/ 目录。它提供了:

  1. 通用依赖:快速创建 Logger, Meter, Context。
  2. 基础设施连接:使用 testcontainers 自动启动和管理 Redis, MySQL, Etcd 等容器。

无需手动启动 Docker 服务testkit 会自动处理容器的生命周期。

3.1 基础工具
import (
    "testing"
    "github.com/ceyewan/genesis/testkit"
)

func TestMyComponent(t *testing.T) {
    // 获取基础工具(Logger, Meter, Context)
    kit := testkit.NewKit(t)

    // 使用 Logger
    kit.Logger.Info("starting test")

    // 生成唯一 ID
    id := testkit.NewID()  // 例如: "a3f7c9e2"
}
3.2 容器化服务 (Integration Test)

所有容器化服务都遵循统一的 API 命名规范:

组件 Config Connector Client/DB
Redis NewRedisContainerConfig NewRedisContainerConnector NewRedisContainerClient
MySQL NewMySQLContainerConfig NewMySQLConnector NewMySQLDB
PostgreSQL NewPostgreSQLContainerConfig NewPostgreSQLConnector NewPostgreSQLDB
Etcd NewEtcdContainerConfig NewEtcdContainerConnector NewEtcdContainerClient
NATS NewNATSContainerConfig NewNATSContainerConnector NewNATSContainerConn
Kafka NewKafkaContainerConfig NewKafkaContainerConnector NewKafkaContainerClient

Redis 示例

func TestRedisCache(t *testing.T) {
    // 自动启动 Redis 容器并获取客户端
    rdb := testkit.NewRedisContainerClient(t)
    // 容器会在测试结束后自动清理

    err := rdb.Set(context.Background(), "key", "value", 0).Err()
    require.NoError(t, err)
}

MySQL 示例

func TestMySQLRepository(t *testing.T) {
    // 自动启动 MySQL 容器并获取 GORM DB
    db := testkit.NewMySQLDB(t)

    // 执行数据库操作...
    result := db.Create(&User{Name: "test"})
    require.NoError(t, result.Error)
}

PostgreSQL 示例

func TestPostgreSQLRepository(t *testing.T) {
    db := testkit.NewPostgreSQLDB(t)
    // 同 MySQL...
}

Etcd 示例

func TestEtcdDLock(t *testing.T) {
    client := testkit.NewEtcdContainerClient(t)
    // 使用 etcd client...
}

NATS 示例

func TestNATSPublish(t *testing.T) {
    nc := testkit.NewNATSContainerConn(t)
    // 使用 NATS 连接...
}

Kafka 示例

func TestKafkaProduce(t *testing.T) {
    client := testkit.NewKafkaContainerClient(t)
    // 使用 Kafka client...
}
3.3 SQLite 测试支持

SQLite 是嵌入式数据库,无需容器,适合快速测试:

// 使用内存数据库
sqliteDB := testkit.NewSQLiteDB(t)

// 使用持久化文件(存储在 t.TempDir())
cfg := testkit.NewPersistentSQLiteConfig(t)
conn := testkit.NewPersistentSQLiteConnector(t)
3.4 Mock 依赖与代码复用
  • 复用原则:凡是可以复用的测试相关代码(如通用接口的 Mock 实现、辅助断言函数等),必须 编写在 testkit 包中,严禁在不同组件中重复编写。
  • Mock 位置:将通用的 Mock 结构体放在 testkit 下(例如 testkit/mock_logger.go)。
  • 单元测试:对于特定组件的私有接口,使用 gomock 或手写 Mock。
3.5 数据隔离策略

为了避免测试间的数据污染和冲突("脏数据"),请遵循以下隔离策略:

  1. MySQL/PostgreSQL:

    • 事务回滚(推荐):在测试开始时开启事务,测试结束时 defer tx.Rollback()
    • 唯一表名:如果无法使用事务,使用 testkit.NewID() 生成唯一的后缀创建临时表。
  2. Redis/Etcd/KV:

    • 随机 Key 前缀:使用 testkit.NewID() 生成唯一的 Key 前缀。
    prefix := "test:" + testkit.NewID() + ":"
    key := prefix + "user:1"
    
    • 自动过期:为测试 Key 设置较短的 TTL。
  3. 消息队列 (NATS/Kafka):

    • 随机 Topic/Subject:使用 testkit.NewID() 生成唯一的名称。
    topic := "test-topic-" + testkit.NewID()
    

4. 最佳实践

4.1 表格驱动测试
func TestAdd(t *testing.T) {
    tests := []struct {
        name string
        a, b int
        want int
    }{
        {"positive", 1, 2, 3},
        {"negative", -1, -2, -3},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if got := Add(tt.a, tt.b); got != tt.want {
                t.Errorf("Add() = %v, want %v", got, tt.want)
            }
        })
    }
}
4.2 并发测试

使用 -race 标志运行测试:

go test -race ./...
4.3 避免全局状态

测试用例之间应相互独立,避免修改全局变量。使用 testkit 获取的连接是独立的实例(每个测试都有自己的容器),天然隔离。

5. CI/CD 集成

CI 流程会自动运行所有测试。testcontainers 确保 CI 环境无需预装任何服务,只要有 Docker 即可运行完整的集成测试。

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewContext

func NewContext(t *testing.T, timeout time.Duration) (context.Context, context.CancelFunc)

NewContext 返回一个带有超时的测试上下文

func NewEtcdContainerClient

func NewEtcdContainerClient(t *testing.T) *clientv3.Client

NewEtcdContainerClient 使用 testcontainers 创建并返回原生 Etcd 客户端 生命周期由 t.Cleanup 管理

func NewEtcdContainerConfig

func NewEtcdContainerConfig(t *testing.T) *connector.EtcdConfig

NewEtcdContainerConfig 使用 testcontainers 创建 Etcd 容器并返回配置 生命周期由 t.Cleanup 管理

func NewEtcdContainerConnector

func NewEtcdContainerConnector(t *testing.T) connector.EtcdConnector

NewEtcdContainerConnector 使用 testcontainers 创建并连接 Etcd 连接器 生命周期由 t.Cleanup 管理

func NewID

func NewID() string

NewID 返回一个唯一的测试 ID (UUID v4 前 8 位) 用于生成唯一的 Key、Topic 或表名后缀,避免测试间数据冲突

func NewKafkaContainerClient

func NewKafkaContainerClient(t *testing.T) *kgo.Client

NewKafkaContainerClient 使用 testcontainers 创建并返回原生 Kafka 客户端 生命周期由 t.Cleanup 管理

func NewKafkaContainerConfig

func NewKafkaContainerConfig(t *testing.T) *connector.KafkaConfig

NewKafkaContainerConfig 使用 testcontainers 创建 Kafka 容器并返回配置 生命周期由 t.Cleanup 管理

func NewKafkaContainerConnector

func NewKafkaContainerConnector(t *testing.T) connector.KafkaConnector

NewKafkaContainerConnector 使用 testcontainers 创建并连接 Kafka 连接器 生命周期由 t.Cleanup 管理

func NewLogger

func NewLogger() clog.Logger

NewLogger 返回一个用于测试的 logger 输出到开发环境格式,适合本地调试

func NewMeter

func NewMeter() metrics.Meter

NewMeter 返回一个用于测试的 meter 使用 Discard 模式,不实际输出指标

func NewMySQLConnector

func NewMySQLConnector(t *testing.T) connector.MySQLConnector

NewMySQLConnector 获取 MySQL 连接器(基于 testcontainers) 生命周期由 t.Cleanup 管理

func NewMySQLContainerConfig

func NewMySQLContainerConfig(t *testing.T) *connector.MySQLConfig

NewMySQLContainerConfig 使用 testcontainers 创建 MySQL 容器并返回配置 生命周期由 t.Cleanup 管理

func NewMySQLDB

func NewMySQLDB(t *testing.T) *gorm.DB

NewMySQLDB 获取 GORM DB 实例(基于 testcontainers)

func NewNATSContainerConfig

func NewNATSContainerConfig(t *testing.T) *connector.NATSConfig

NewNATSContainerConfig 使用 testcontainers 创建 NATS 容器并返回配置 生命周期由 t.Cleanup 管理

func NewNATSContainerConn

func NewNATSContainerConn(t *testing.T) *nats.Conn

NewNATSContainerConn 使用 testcontainers 创建并返回原生 NATS 连接 生命周期由 t.Cleanup 管理

func NewNATSContainerConnector

func NewNATSContainerConnector(t *testing.T) connector.NATSConnector

NewNATSContainerConnector 使用 testcontainers 创建并连接 NATS 连接器 生命周期由 t.Cleanup 管理

func NewPersistentSQLiteConfig

func NewPersistentSQLiteConfig(t *testing.T) *connector.SQLiteConfig

NewPersistentSQLiteConfig 返回持久化 SQLite 测试配置 用于需要文件持久化的测试场景,数据库文件存储在 t.TempDir() 中

func NewPersistentSQLiteConnector

func NewPersistentSQLiteConnector(t *testing.T) connector.SQLiteConnector

NewPersistentSQLiteConnector 获取持久化 SQLite 连接器 数据库文件存储在临时目录中,测试结束后自动清理 生命周期由 t.Cleanup 管理

func NewPostgreSQLConnector

func NewPostgreSQLConnector(t *testing.T) connector.PostgreSQLConnector

NewPostgreSQLConnector 获取 PostgreSQL 连接器(基于 testcontainers) 生命周期由 t.Cleanup 管理

func NewPostgreSQLContainerConfig

func NewPostgreSQLContainerConfig(t *testing.T) *connector.PostgreSQLConfig

NewPostgreSQLContainerConfig 使用 testcontainers 创建 PostgreSQL 容器并返回配置 生命周期由 t.Cleanup 管理

func NewPostgreSQLDB

func NewPostgreSQLDB(t *testing.T) *gorm.DB

NewPostgreSQLDB 获取 GORM DB 实例(基于 testcontainers)

func NewRedisContainerClient

func NewRedisContainerClient(t *testing.T) *redis.Client

NewRedisContainerClient 使用 testcontainers 创建并返回原生 Redis 客户端 生命周期由 t.Cleanup 管理

func NewRedisContainerConfig

func NewRedisContainerConfig(t *testing.T) *connector.RedisConfig

NewRedisContainerConfig 使用 testcontainers 创建 Redis 容器并返回配置 生命周期由 t.Cleanup 管理

func NewRedisContainerConnector

func NewRedisContainerConnector(t *testing.T) connector.RedisConnector

NewRedisContainerConnector 使用 testcontainers 创建并连接 Redis 连接器 生命周期由 t.Cleanup 管理

func NewSQLiteConfig

func NewSQLiteConfig() *connector.SQLiteConfig

NewSQLiteContainerConfig 返回 SQLite 内存数据库配置 默认使用内存数据库,测试结束后自动清理

func NewSQLiteConnector

func NewSQLiteConnector(t *testing.T) connector.SQLiteConnector

NewSQLiteConnector 获取 SQLite 连接器(内存数据库) 生命周期由 t.Cleanup 管理

func NewSQLiteDB

func NewSQLiteDB(t *testing.T) *gorm.DB

NewSQLiteDB 获取 GORM DB 实例(内存数据库)

Types

type Kit

type Kit struct {
	Ctx    context.Context
	Logger clog.Logger
	Meter  metrics.Meter
}

Kit 包含通用的测试依赖

func NewKit

func NewKit(t *testing.T) *Kit

NewKit 返回一个包含默认依赖的测试工具包

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL