1 介绍

1.1 定义

01.技术概述
    a.基本概念
        testify是Go语言生态系统中最流行的测试工具库,由Stretchr团队开发和维护。它提供了一套完整的测试工具集,包括断言assertion、模拟mock和测试套件suite等功能,旨在简化Go语言的测试代码编写,提高测试代码的可读性和可维护性。testify已经成为Go社区事实上的测试标准库扩展。
    b.核心定位
        testify定位为Go标准testing包的增强工具库,而非替代品。它在保持Go语言简洁哲学的基础上,提供了更丰富的断言方法、强大的mock功能和灵活的测试套件组织能力。testify与标准库完全兼容,可以无缝集成到现有的测试框架中。
    c.发展历程
        testify于2012年首次发布,经过十多年的发展,已经成为GitHub上Star数量最多的Go测试库之一,拥有超过2万个Star。项目活跃度高,社区贡献者众多,版本迭代稳定,是Go语言测试领域最成熟的解决方案之一。

02.设计理念
    a.简洁优雅
        a.链式断言
            testify提供流畅的链式断言API,让测试代码读起来更像自然语言,显著提高了代码的可读性。每个断言方法都返回布尔值,支持灵活的错误处理。
        b.代码示例
            ---
            // testify基础示例:简单的断言测试
            package example

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

            func Add(a, b int) int {
                return a + b
            }

            // 测试函数:使用testify的assert包
            func TestAdd(t *testing.T) {
                // 创建断言对象
                assert := assert.New(t)

                // 基础相等断言
                result := Add(2, 3)
                assert.Equal(5, result, "2 + 3 应该等于 5")

                // 不等断言
                assert.NotEqual(6, result, "结果不应该等于 6")

                // 大于断言
                assert.Greater(result, 4, "结果应该大于 4")

                // 零值断言
                assert.NotZero(result, "结果不应该是零值")
            }

            // 测试函数:使用require包(断言失败立即终止)
            func TestAddWithRequire(t *testing.T) {
                require := require.New(t)

                result := Add(2, 3)
                // require断言失败会立即终止测试
                require.Equal(5, result, "2 + 3 必须等于 5")

                // 如果上面的断言失败,这行代码不会执行
                require.Positive(result, "结果必须是正数")
            }
            ---
    b.功能完整
        a.全面的断言方法
            testify提供超过100个断言方法,覆盖相等性判断、类型检查、集合操作、错误处理、panic捕获等各种测试场景,几乎涵盖了所有常见的测试需求。
        b.强大的Mock能力
            内置完整的mock框架,支持接口mock、方法期望设置、参数匹配、返回值控制和调用验证。相比手写mock对象,testify的mock功能大大简化了依赖隔离的测试代码编写。
        c.灵活的测试套件
            提供suite包实现测试套件功能,支持SetUp和TearDown钩子函数、测试夹具管理、子测试组织等高级特性,帮助开发者构建结构化的测试代码。

03.核心价值
    a.提升开发效率
        a.减少样板代码
            使用testify可以大幅减少测试代码中的样板代码。传统的Go测试需要大量的if判断和t.Errorf调用,而testify通过简洁的断言API将多行代码压缩为一行。
        b.对比示例
            ---
            // 对比:标准库 vs testify
            package example

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

            // 使用标准库testing的测试(冗长)
            func TestWithStandardLibrary(t *testing.T) {
                result := Add(2, 3)

                if result != 5 {
                    t.Errorf("Add(2, 3) = %d; want 5", result)
                }

                if result == 6 {
                    t.Errorf("Add(2, 3) should not equal 6")
                }

                if result <= 4 {
                    t.Errorf("Add(2, 3) = %d; want > 4", result)
                }
            }

            // 使用testify的测试(简洁)
            func TestWithTestify(t *testing.T) {
                result := Add(2, 3)

                // 三个断言,代码清晰简洁
                assert.Equal(t, 5, result)
                assert.NotEqual(t, 6, result)
                assert.Greater(t, result, 4)
            }

            // 更复杂的场景:切片比较
            func TestSliceComparison(t *testing.T) {
                expected := []int{1, 2, 3}
                actual := []int{1, 2, 3}

                // 标准库需要手动遍历比较
                if len(expected) != len(actual) {
                    t.Errorf("length mismatch")
                }
                for i := range expected {
                    if expected[i] != actual[i] {
                        t.Errorf("element %d: got %d, want %d", i, actual[i], expected[i])
                    }
                }

                // testify一行搞定
                assert.Equal(t, expected, actual)
            }
            ---
    b.提高代码质量
        a.清晰的错误信息
            testify在断言失败时提供详细的错误信息,包括期望值、实际值和差异对比,帮助开发者快速定位问题。支持自定义错误消息,进一步增强调试体验。
        b.强制最佳实践
            通过require包强制关键断言必须通过,避免测试在错误状态下继续执行。通过mock验证强制检查依赖调用,确保测试覆盖了所有关键路径。
    c.促进测试文化
        降低测试代码编写门槛,让开发者更愿意编写测试。提供统一的测试代码风格,提高团队协作效率。丰富的功能支持促进TDD开发模式的实践。

1.2 核心概念

01.断言系统
    a.assert包
        a.功能定位
            assert包提供非致命性断言,断言失败时会记录错误信息但不会终止测试执行。适用于需要检查多个条件的场景,即使某个断言失败,后续断言仍会继续执行,帮助一次性发现多个问题。
        b.使用方式
            ---
            // assert包使用示例:非致命性断言
            package example

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

            func TestAssertExample(t *testing.T) {
                // 方式1:每次调用都传递t参数
                assert.Equal(t, 5, Add(2, 3), "基础加法测试")
                assert.NotNil(t, "hello")

                // 方式2:创建断言对象,避免重复传递t
                a := assert.New(t)
                a.Equal(5, Add(2, 3))
                a.NotNil("hello")

                // 即使前面的断言失败,这里仍会执行
                a.True(true, "这个断言会执行")
            }

            // 多个断言的测试示例
            func TestMultipleAssertions(t *testing.T) {
                a := assert.New(t)

                user := GetUser(1)

                // 检查多个字段,即使某个失败也继续检查
                a.NotNil(user, "用户不应为nil")
                a.Equal("Alice", user.Name, "用户名应为Alice")
                a.Equal(25, user.Age, "年龄应为25")
                a.True(user.Active, "用户应处于激活状态")

                // 所有断言都会执行,最后统一报告失败信息
            }

            type User struct {
                Name   string
                Age    int
                Active bool
            }

            func GetUser(id int) *User {
                return &User{Name: "Alice", Age: 25, Active: true}
            }
            ---
    b.require包
        a.功能定位
            require包提供致命性断言,断言失败时会立即终止当前测试函数的执行。适用于前置条件检查,如果前置条件不满足,继续执行测试没有意义且可能导致panic。
        b.使用场景
            ---
            // require包使用示例:致命性断言
            package example

            import (
                "testing"
                "github.com/stretchr/testify/require"
                "database/sql"
            )

            func TestRequireExample(t *testing.T) {
                r := require.New(t)

                // 数据库连接必须成功,否则后续测试无法进行
                db, err := sql.Open("mysql", "connection_string")
                r.NoError(err, "数据库连接必须成功")
                r.NotNil(db, "数据库对象不能为nil")

                // 如果上面的require失败,这里的代码不会执行
                defer db.Close()

                // 查询必须成功
                rows, err := db.Query("SELECT * FROM users")
                r.NoError(err)
                defer rows.Close()

                // 至少要有一条记录
                r.True(rows.Next(), "至少应该有一条用户记录")
            }

            // require vs assert 选择示例
            func TestRequireVsAssert(t *testing.T) {
                r := require.New(t)
                a := assert.New(t)

                // 使用require检查前置条件
                config := LoadConfig()
                r.NotNil(config, "配置文件必须加载成功")

                // 使用assert检查多个配置项
                a.Equal("localhost", config.Host)
                a.Equal(3306, config.Port)
                a.Equal("utf8mb4", config.Charset)

                // 即使某个配置项不对,也能看到所有配置的实际值
            }

            type Config struct {
                Host    string
                Port    int
                Charset string
            }

            func LoadConfig() *Config {
                return &Config{Host: "localhost", Port: 3306, Charset: "utf8mb4"}
            }
            ---

02.Mock系统
    a.Mock对象
        a.基本原理
            testify的mock包提供了一套完整的模拟对象框架,通过继承mock.Mock结构体并实现接口方法,可以轻松创建mock对象。mock对象可以设置方法调用期望、模拟返回值、验证调用次数和参数,实现完全的依赖隔离。
        b.工作流程
            Mock测试的典型流程包括三个步骤:定义期望On、执行被测代码、验证调用AssertExpectations。这种结构化的流程确保测试的完整性和可靠性。
        c.代码示例
            ---
            // Mock对象使用示例
            package example

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

            // 定义接口
            type UserRepository interface {
                GetUserByID(id int) (*User, error)
                SaveUser(user *User) error
            }

            // 创建Mock对象(继承mock.Mock)
            type MockUserRepository struct {
                mock.Mock
            }

            // 实现接口方法
            func (m *MockUserRepository) GetUserByID(id int) (*User, error) {
                // 调用mock框架记录调用并返回预设值
                args := m.Called(id)
                return args.Get(0).(*User), args.Error(1)
            }

            func (m *MockUserRepository) SaveUser(user *User) error {
                args := m.Called(user)
                return args.Error(0)
            }

            // 被测试的服务
            type UserService struct {
                repo UserRepository
            }

            func (s *UserService) GetUserName(id int) (string, error) {
                user, err := s.repo.GetUserByID(id)
                if err != nil {
                    return "", err
                }
                return user.Name, nil
            }

            // Mock测试示例
            func TestUserService_GetUserName(t *testing.T) {
                // 1. 创建mock对象
                mockRepo := new(MockUserRepository)

                // 2. 设置期望:当调用GetUserByID(1)时返回指定用户
                expectedUser := &User{Name: "Alice", Age: 25}
                mockRepo.On("GetUserByID", 1).Return(expectedUser, nil)

                // 3. 创建被测服务,注入mock对象
                service := &UserService{repo: mockRepo}

                // 4. 执行测试
                name, err := service.GetUserName(1)

                // 5. 断言结果
                assert.NoError(t, err)
                assert.Equal(t, "Alice", name)

                // 6. 验证mock对象的方法被正确调用
                mockRepo.AssertExpectations(t)
            }
            ---
    b.参数匹配器
        a.功能说明
            testify提供多种参数匹配器,包括精确匹配、类型匹配mock.Anything、自定义匹配mock.MatchedBy等。参数匹配器增强了mock的灵活性,可以处理复杂的参数验证场景。
        b.使用示例
            ---
            // 参数匹配器示例
            package example

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

            func TestParameterMatchers(t *testing.T) {
                mockRepo := new(MockUserRepository)

                // 精确匹配:只匹配特定参数
                mockRepo.On("GetUserByID", 1).Return(&User{Name: "Alice"}, nil)

                // Anything匹配:匹配任意参数
                mockRepo.On("GetUserByID", mock.Anything).Return(&User{Name: "Default"}, nil)

                // AnythingOfType匹配:匹配特定类型
                mockRepo.On("SaveUser", mock.AnythingOfType("*example.User")).Return(nil)

                // MatchedBy自定义匹配:使用函数进行复杂匹配
                mockRepo.On("GetUserByID", mock.MatchedBy(func(id int) bool {
                    return id > 0 && id < 1000
                })).Return(&User{Name: "ValidID"}, nil)

                // 测试执行
                user1, _ := mockRepo.GetUserByID(1)
                assert.Equal(t, "Alice", user1.Name)

                user2, _ := mockRepo.GetUserByID(999)
                assert.Equal(t, "ValidID", user2.Name)

                err := mockRepo.SaveUser(&User{Name: "Bob"})
                assert.NoError(t, err)

                mockRepo.AssertExpectations(t)
            }
            ---

03.测试套件
    a.Suite结构
        a.基本概念
            testify的suite包提供测试套件功能,通过将测试方法组织到结构体中,可以实现测试的结构化管理。Suite支持SetUp和TearDown钩子函数,方便管理测试的前置和后置操作。
        b.核心方法
            Suite提供多个生命周期钩子:SetupSuite在整个套件开始前执行一次,TearDownSuite在套件结束后执行一次,SetupTest在每个测试方法前执行,TearDownTest在每个测试方法后执行。这种分层的生命周期管理使测试更加规范和可控。
        c.代码示例
            ---
            // 测试套件示例
            package example

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

            // 定义测试套件结构体
            type UserTestSuite struct {
                suite.Suite
                repo    UserRepository
                service *UserService
            }

            // SetupSuite:在整个套件开始前执行一次
            func (s *UserTestSuite) SetupSuite() {
                // 初始化数据库连接等全局资源
                s.repo = NewUserRepository()
            }

            // TearDownSuite:在套件结束后执行一次
            func (s *UserTestSuite) TearDownSuite() {
                // 清理全局资源
                s.repo.Close()
            }

            // SetupTest:在每个测试方法前执行
            func (s *UserTestSuite) SetupTest() {
                // 初始化测试数据
                s.service = &UserService{repo: s.repo}
            }

            // TearDownTest:在每个测试方法后执行
            func (s *UserTestSuite) TearDownTest() {
                // 清理测试数据
                s.repo.CleanTestData()
            }

            // 测试方法:必须以Test开头
            func (s *UserTestSuite) TestGetUserName() {
                name, err := s.service.GetUserName(1)
                assert.NoError(s.T(), err)
                assert.Equal(s.T(), "Alice", name)
            }

            func (s *UserTestSuite) TestSaveUser() {
                user := &User{Name: "Bob", Age: 30}
                err := s.repo.SaveUser(user)
                assert.NoError(s.T(), err)
            }

            // 运行测试套件
            func TestUserTestSuite(t *testing.T) {
                suite.Run(t, new(UserTestSuite))
            }

            // 辅助函数(示例)
            func NewUserRepository() UserRepository {
                return &MockUserRepository{}
            }

            func (m *MockUserRepository) Close() {}
            func (m *MockUserRepository) CleanTestData() {}
            ---
    b.测试组织
        Suite允许将相关测试组织在一起,共享测试夹具和辅助方法。通过套件继承可以实现测试代码的复用,适合大型项目的测试管理。

1.3 优缺点

01.优势分析
    a.简化测试代码
        a.减少代码量
            相比使用Go标准库编写测试,testify可以将测试代码量减少50%以上。通过简洁的断言API和自动化的错误信息生成,大幅降低了测试代码的复杂度。
        b.对比示例
            ---
            // 标准库 vs testify 代码量对比
            package example

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

            // 场景1:基础断言对比
            func TestBasicStandardLib(t *testing.T) {
                result := Add(2, 3)
                if result != 5 {
                    t.Errorf("Add(2, 3) = %d; want 5", result)
                }
                if result == 0 {
                    t.Errorf("Add(2, 3) should not be zero")
                }
                if result <= 4 {
                    t.Errorf("Add(2, 3) = %d; should be > 4", result)
                }
            }

            func TestBasicTestify(t *testing.T) {
                result := Add(2, 3)
                assert.Equal(t, 5, result)
                assert.NotZero(t, result)
                assert.Greater(t, result, 4)
            }

            // 场景2:复杂结构体对比
            func TestStructStandardLib(t *testing.T) {
                expected := &User{Name: "Alice", Age: 25, Active: true}
                actual := GetUser(1)

                if actual == nil {
                    t.Fatal("user should not be nil")
                }
                if actual.Name != expected.Name {
                    t.Errorf("Name = %s; want %s", actual.Name, expected.Name)
                }
                if actual.Age != expected.Age {
                    t.Errorf("Age = %d; want %d", actual.Age, expected.Age)
                }
                if actual.Active != expected.Active {
                    t.Errorf("Active = %v; want %v", actual.Active, expected.Active)
                }
            }

            func TestStructTestify(t *testing.T) {
                expected := &User{Name: "Alice", Age: 25, Active: true}
                actual := GetUser(1)

                assert.Equal(t, expected, actual)
            }

            // 场景3:切片对比
            func TestSliceStandardLib(t *testing.T) {
                expected := []int{1, 2, 3, 4, 5}
                actual := GetNumbers()

                if len(actual) != len(expected) {
                    t.Fatalf("length = %d; want %d", len(actual), len(expected))
                }
                if !reflect.DeepEqual(expected, actual) {
                    t.Errorf("slice = %v; want %v", actual, expected)
                }
                for i, v := range expected {
                    if actual[i] != v {
                        t.Errorf("element[%d] = %d; want %d", i, actual[i], v)
                    }
                }
            }

            func TestSliceTestify(t *testing.T) {
                expected := []int{1, 2, 3, 4, 5}
                actual := GetNumbers()

                assert.Equal(t, expected, actual)
                assert.ElementsMatch(t, expected, actual)
            }

            func GetNumbers() []int {
                return []int{1, 2, 3, 4, 5}
            }
            ---
    b.丰富的断言方法
        a.全面覆盖
            testify提供超过100个断言方法,涵盖基础比较Equal、NotEqual,类型检查IsType、Implements,集合操作Contains、ElementsMatch,错误处理Error、NoError,panic捕获Panics、NotPanics等各种场景。
        b.常用断言分类
            相等性断言包括Equal、NotEqual、Same、NotSame;数值断言包括Greater、GreaterOrEqual、Less、LessOrEqual、Positive、Negative;字符串断言包括Contains、NotContains、Regexp、NotRegexp;集合断言包括Len、Empty、Contains、ElementsMatch、Subset。
    c.强大的Mock能力
        a.完整的Mock框架
            testify提供完整的mock功能,无需第三方工具即可实现接口mock。支持方法期望设置、参数匹配、返回值控制、调用次数验证、调用顺序验证等高级特性。
        b.Mock示例
            ---
            // 高级Mock功能示例
            package example

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

            // 场景1:调用次数验证
            func TestCallTimes(t *testing.T) {
                mockRepo := new(MockUserRepository)

                // 期望方法被调用1次
                mockRepo.On("GetUserByID", 1).Return(&User{Name: "Alice"}, nil).Once()

                // 期望方法被调用2次
                mockRepo.On("SaveUser", mock.Anything).Return(nil).Twice()

                service := &UserService{repo: mockRepo}

                // 调用1次GetUserByID
                service.GetUserName(1)

                // 调用2次SaveUser
                mockRepo.SaveUser(&User{Name: "Bob"})
                mockRepo.SaveUser(&User{Name: "Charlie"})

                // 验证调用次数
                mockRepo.AssertExpectations(t)
                mockRepo.AssertNumberOfCalls(t, "GetUserByID", 1)
                mockRepo.AssertNumberOfCalls(t, "SaveUser", 2)
            }

            // 场景2:不同参数返回不同值
            func TestDifferentReturns(t *testing.T) {
                mockRepo := new(MockUserRepository)

                // 不同ID返回不同用户
                mockRepo.On("GetUserByID", 1).Return(&User{Name: "Alice"}, nil)
                mockRepo.On("GetUserByID", 2).Return(&User{Name: "Bob"}, nil)
                mockRepo.On("GetUserByID", 999).Return(nil, errors.New("not found"))

                user1, err1 := mockRepo.GetUserByID(1)
                assert.NoError(t, err1)
                assert.Equal(t, "Alice", user1.Name)

                user2, err2 := mockRepo.GetUserByID(2)
                assert.NoError(t, err2)
                assert.Equal(t, "Bob", user2.Name)

                user3, err3 := mockRepo.GetUserByID(999)
                assert.Error(t, err3)
                assert.Nil(t, user3)

                mockRepo.AssertExpectations(t)
            }

            // 场景3:使用Run自定义行为
            func TestCustomBehavior(t *testing.T) {
                mockRepo := new(MockUserRepository)

                callCount := 0
                mockRepo.On("GetUserByID", mock.Anything).Run(func(args mock.Arguments) {
                    callCount++
                    id := args.Int(0)
                    t.Logf("GetUserByID called with id=%d, total calls=%d", id, callCount)
                }).Return(&User{Name: "Dynamic"}, nil)

                mockRepo.GetUserByID(1)
                mockRepo.GetUserByID(2)
                mockRepo.GetUserByID(3)

                assert.Equal(t, 3, callCount)
                mockRepo.AssertExpectations(t)
            }

            // 场景4:测试超时场景
            func TestTimeout(t *testing.T) {
                mockRepo := new(MockUserRepository)

                // 模拟超时错误
                mockRepo.On("GetUserByID", mock.Anything).
                    WaitUntil(time.After(100 * time.Millisecond)).
                    Return(nil, errors.New("timeout"))

                start := time.Now()
                _, err := mockRepo.GetUserByID(1)
                duration := time.Since(start)

                assert.Error(t, err)
                assert.GreaterOrEqual(t, duration, 100*time.Millisecond)
                mockRepo.AssertExpectations(t)
            }
            ---
    d.良好的测试组织
        suite包提供结构化的测试组织能力,支持SetUp/TearDown钩子、测试夹具管理、子测试分组等功能。相比零散的测试函数,suite让测试代码更加模块化和可维护。

02.局限性分析
    a.学习成本
        a.API数量庞大
            testify提供超过100个断言方法和多个包assert、require、mock、suite、http,初学者需要时间熟悉各个API的用途和差异。不过常用的断言方法只有20个左右,掌握核心API即可应对大部分场景。
        b.Mock理解门槛
            Mock概念对测试新手来说有一定理解难度,需要理解测试替身、依赖注入、期望设置等概念。建议从简单的断言开始,逐步学习mock和suite高级功能。
    b.性能开销
        a.反射使用
            testify的断言实现大量使用反射进行深度比较和类型检查,在高频调用场景下可能带来性能开销。不过对于绝大多数测试场景,这点性能开销可以忽略不计。
        b.基准测试对比
            ---
            // 性能对比基准测试
            package example

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

            // 标准库断言性能
            func BenchmarkStandardLibAssertion(b *testing.B) {
                for i := 0; i < b.N; i++ {
                    result := Add(2, 3)
                    if result != 5 {
                        b.Errorf("unexpected result")
                    }
                }
            }

            // testify断言性能
            func BenchmarkTestifyAssertion(b *testing.B) {
                for i := 0; i < b.N; i++ {
                    result := Add(2, 3)
                    assert.Equal(b, 5, result)
                }
            }

            // 复杂对象深度比较性能
            func BenchmarkDeepEqual(b *testing.B) {
                user1 := &User{Name: "Alice", Age: 25, Active: true}
                user2 := &User{Name: "Alice", Age: 25, Active: true}

                b.Run("StandardLib", func(b *testing.B) {
                    for i := 0; i < b.N; i++ {
                        if user1.Name != user2.Name ||
                           user1.Age != user2.Age ||
                           user1.Active != user2.Active {
                            b.Errorf("not equal")
                        }
                    }
                })

                b.Run("Testify", func(b *testing.B) {
                    for i := 0; i < b.N; i++ {
                        assert.Equal(b, user1, user2)
                    }
                })
            }
            // 结果:testify比标准库慢约2-3倍,但绝对时间仍在纳秒级
            ---
    c.依赖管理
        testify作为第三方库需要通过go mod管理依赖,增加了项目的外部依赖。不过testify本身非常稳定,版本兼容性好,升级风险低。
    d.过度使用风险
        a.断言滥用
            过度使用断言可能导致测试代码臃肿,一个测试函数包含几十个断言会降低可读性。建议遵循单一职责原则,每个测试函数只测试一个行为。
        b.Mock滥用
            过度使用mock可能导致测试与实现耦合过紧,实现细节的改变会导致大量测试失败。应该只mock外部依赖接口、数据库、网络请求等,而不是mock内部逻辑。

03.适用场景评估
    a.最佳适用场景
        a.单元测试
            testify最适合编写单元测试,丰富的断言方法和强大的mock能力能够快速验证函数行为和隔离依赖。特别适合测试业务逻辑层、服务层代码。
        b.API测试
            testify的http包提供HTTP测试工具,配合httptest包可以轻松测试HTTP处理器和RESTful API。适合测试Web应用、微服务接口。
        c.集成测试
            suite包的SetUp/TearDown机制非常适合管理集成测试的环境准备和清理。可以在SetupSuite中初始化数据库、启动服务,在TearDownSuite中清理资源。
        d.TDD开发
            testify简洁的API降低了编写测试的门槛,鼓励开发者采用TDD测试驱动开发模式。先写测试再写实现,提高代码质量。
    b.不太适合的场景
        a.端到端测试
            对于需要浏览器自动化的端到端测试,testify不是最佳选择。这类测试应该使用Selenium、Cypress等专业工具。
        b.性能测试
            虽然testify可以配合Go的基准测试使用,但对于复杂的性能测试和压力测试,建议使用专业工具如wrk、ab、JMeter等。
        c.极简项目
            对于非常小型的项目或脚本,引入testify可能过于重量级。如果只需要几个简单的相等性检查,使用标准库testing包更简单。

1.4 使用场景

01.单元测试场景
    a.业务逻辑测试
        a.场景描述
            业务逻辑层是应用的核心,包含各种业务规则、数据验证、计算逻辑等。testify的断言能力和mock功能可以快速验证业务逻辑的正确性,同时隔离外部依赖如数据库、缓存、第三方API等。
        b.实战示例
            ---
            // 业务逻辑测试示例:订单处理服务
            package business

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

            // 订单服务接口
            type OrderService struct {
                orderRepo    OrderRepository
                paymentRepo  PaymentRepository
                notification NotificationService
            }

            // 创建订单业务逻辑
            func (s *OrderService) CreateOrder(userID int, items []OrderItem, amount float64) (*Order, error) {
                // 1. 验证金额
                if amount <= 0 {
                    return nil, errors.New("订单金额必须大于0")
                }

                // 2. 验证商品列表
                if len(items) == 0 {
                    return nil, errors.New("订单至少包含一个商品")
                }

                // 3. 创建订单
                order := &Order{
                    UserID: userID,
                    Items:  items,
                    Amount: amount,
                    Status: "pending",
                }

                // 4. 保存订单
                err := s.orderRepo.Save(order)
                if err != nil {
                    return nil, err
                }

                // 5. 发送通知
                s.notification.Send(userID, "订单创建成功")

                return order, nil
            }

            // Mock对象定义
            type MockOrderRepository struct {
                mock.Mock
            }

            func (m *MockOrderRepository) Save(order *Order) error {
                args := m.Called(order)
                return args.Error(0)
            }

            type MockPaymentRepository struct {
                mock.Mock
            }

            type MockNotificationService struct {
                mock.Mock
            }

            func (m *MockNotificationService) Send(userID int, message string) error {
                args := m.Called(userID, message)
                return args.Error(0)
            }

            // 测试用例:成功创建订单
            func TestCreateOrder_Success(t *testing.T) {
                // 准备mock对象
                mockOrderRepo := new(MockOrderRepository)
                mockNotification := new(MockNotificationService)

                // 设置期望
                mockOrderRepo.On("Save", mock.AnythingOfType("*business.Order")).Return(nil)
                mockNotification.On("Send", 1, "订单创建成功").Return(nil)

                // 创建服务
                service := &OrderService{
                    orderRepo:    mockOrderRepo,
                    notification: mockNotification,
                }

                // 执行测试
                items := []OrderItem{{ProductID: 1, Quantity: 2, Price: 50.0}}
                order, err := service.CreateOrder(1, items, 100.0)

                // 断言结果
                assert.NoError(t, err)
                assert.NotNil(t, order)
                assert.Equal(t, 1, order.UserID)
                assert.Equal(t, 100.0, order.Amount)
                assert.Equal(t, "pending", order.Status)
                assert.Len(t, order.Items, 1)

                // 验证mock调用
                mockOrderRepo.AssertExpectations(t)
                mockNotification.AssertExpectations(t)
            }

            // 测试用例:金额验证失败
            func TestCreateOrder_InvalidAmount(t *testing.T) {
                service := &OrderService{}
                items := []OrderItem{{ProductID: 1, Quantity: 1, Price: 50.0}}

                // 测试零金额
                order, err := service.CreateOrder(1, items, 0)
                assert.Error(t, err)
                assert.Nil(t, order)
                assert.Equal(t, "订单金额必须大于0", err.Error())

                // 测试负金额
                order, err = service.CreateOrder(1, items, -100)
                assert.Error(t, err)
                assert.Nil(t, order)
            }

            // 测试用例:空商品列表
            func TestCreateOrder_EmptyItems(t *testing.T) {
                service := &OrderService{}

                order, err := service.CreateOrder(1, []OrderItem{}, 100.0)
                assert.Error(t, err)
                assert.Nil(t, order)
                assert.Contains(t, err.Error(), "至少包含一个商品")
            }

            // 测试用例:数据库保存失败
            func TestCreateOrder_SaveFailure(t *testing.T) {
                mockOrderRepo := new(MockOrderRepository)
                mockOrderRepo.On("Save", mock.Anything).Return(errors.New("数据库错误"))

                service := &OrderService{orderRepo: mockOrderRepo}
                items := []OrderItem{{ProductID: 1, Quantity: 1, Price: 50.0}}

                order, err := service.CreateOrder(1, items, 50.0)
                assert.Error(t, err)
                assert.Nil(t, order)
                assert.Equal(t, "数据库错误", err.Error())

                mockOrderRepo.AssertExpectations(t)
            }

            // 数据结构定义
            type Order struct {
                ID     int
                UserID int
                Items  []OrderItem
                Amount float64
                Status string
            }

            type OrderItem struct {
                ProductID int
                Quantity  int
                Price     float64
            }

            type OrderRepository interface {
                Save(order *Order) error
            }

            type PaymentRepository interface{}

            type NotificationService interface {
                Send(userID int, message string) error
            }
            ---
    b.数据转换测试
        a.场景描述
            数据转换逻辑包括DTO转换、序列化反序列化、数据格式化等。testify的Equal、JSONEq等断言方法可以轻松验证转换结果的正确性。
        b.实战示例
            ---
            // 数据转换测试示例
            package converter

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

            // DTO转换器
            type UserConverter struct{}

            func (c *UserConverter) ToDTO(user *User) *UserDTO {
                if user == nil {
                    return nil
                }

                return &UserDTO{
                    ID:        user.ID,
                    Username:  user.Username,
                    Email:     user.Email,
                    CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"),
                    IsActive:  user.Active,
                }
            }

            func (c *UserConverter) ToEntity(dto *UserDTO) (*User, error) {
                if dto == nil {
                    return nil, errors.New("dto不能为空")
                }

                createdAt, err := time.Parse("2006-01-02 15:04:05", dto.CreatedAt)
                if err != nil {
                    return nil, err
                }

                return &User{
                    ID:        dto.ID,
                    Username:  dto.Username,
                    Email:     dto.Email,
                    CreatedAt: createdAt,
                    Active:    dto.IsActive,
                }, nil
            }

            // 测试DTO转换
            func TestToDTO(t *testing.T) {
                converter := &UserConverter{}

                // 测试正常转换
                createdAt := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC)
                user := &User{
                    ID:        1,
                    Username:  "alice",
                    Email:     "[email protected]",
                    CreatedAt: createdAt,
                    Active:    true,
                }

                dto := converter.ToDTO(user)

                assert.NotNil(t, dto)
                assert.Equal(t, 1, dto.ID)
                assert.Equal(t, "alice", dto.Username)
                assert.Equal(t, "[email protected]", dto.Email)
                assert.Equal(t, "2024-01-01 12:00:00", dto.CreatedAt)
                assert.True(t, dto.IsActive)

                // 测试nil处理
                nilDTO := converter.ToDTO(nil)
                assert.Nil(t, nilDTO)
            }

            // 测试Entity转换
            func TestToEntity(t *testing.T) {
                converter := &UserConverter{}

                dto := &UserDTO{
                    ID:        1,
                    Username:  "bob",
                    Email:     "[email protected]",
                    CreatedAt: "2024-01-01 12:00:00",
                    IsActive:  true,
                }

                user, err := converter.ToEntity(dto)

                assert.NoError(t, err)
                assert.NotNil(t, user)
                assert.Equal(t, 1, user.ID)
                assert.Equal(t, "bob", user.Username)
                assert.Equal(t, "[email protected]", user.Email)
                assert.True(t, user.Active)
                assert.Equal(t, 2024, user.CreatedAt.Year())

                // 测试nil处理
                nilUser, err := converter.ToEntity(nil)
                assert.Error(t, err)
                assert.Nil(t, nilUser)

                // 测试无效日期
                invalidDTO := &UserDTO{CreatedAt: "invalid-date"}
                invalidUser, err := converter.ToEntity(invalidDTO)
                assert.Error(t, err)
                assert.Nil(t, invalidUser)
            }

            // 测试JSON序列化
            func TestJSONSerialization(t *testing.T) {
                user := &User{
                    ID:       1,
                    Username: "charlie",
                    Email:    "[email protected]",
                    Active:   true,
                }

                // 序列化
                jsonData, err := json.Marshal(user)
                assert.NoError(t, err)

                // 使用JSONEq断言JSON相等(忽略字段顺序和格式)
                expectedJSON := `{
                    "id": 1,
                    "username": "charlie",
                    "email": "[email protected]",
                    "active": true
                }`
                assert.JSONEq(t, expectedJSON, string(jsonData))

                // 反序列化
                var decoded User
                err = json.Unmarshal(jsonData, &decoded)
                assert.NoError(t, err)
                assert.Equal(t, user.ID, decoded.ID)
                assert.Equal(t, user.Username, decoded.Username)
            }

            type User struct {
                ID        int       `json:"id"`
                Username  string    `json:"username"`
                Email     string    `json:"email"`
                CreatedAt time.Time `json:"created_at"`
                Active    bool      `json:"active"`
            }

            type UserDTO struct {
                ID        int    `json:"id"`
                Username  string `json:"username"`
                Email     string `json:"email"`
                CreatedAt string `json:"created_at"`
                IsActive  bool   `json:"is_active"`
            }
            ---

02.集成测试场景
    a.数据库集成测试
        a.场景描述
            测试应用与数据库的集成,验证SQL查询、事务处理、数据一致性等。suite包的SetUp/TearDown机制非常适合管理测试数据库的初始化和清理。
        b.实战示例
            ---
            // 数据库集成测试示例
            package repository

            import (
                "testing"
                "github.com/stretchr/testify/suite"
                "github.com/stretchr/testify/assert"
                "database/sql"
                _ "github.com/go-sql-driver/mysql"
            )

            // 数据库测试套件
            type UserRepositoryTestSuite struct {
                suite.Suite
                db   *sql.DB
                repo *UserRepository
            }

            // 套件初始化:连接测试数据库
            func (s *UserRepositoryTestSuite) SetupSuite() {
                var err error
                s.db, err = sql.Open("mysql", "root:password@tcp(localhost:3306)/test_db")
                s.Require().NoError(err)

                s.repo = NewUserRepository(s.db)
            }

            // 套件清理:关闭数据库连接
            func (s *UserRepositoryTestSuite) TearDownSuite() {
                if s.db != nil {
                    s.db.Close()
                }
            }

            // 每个测试前:清空表并插入测试数据
            func (s *UserRepositoryTestSuite) SetupTest() {
                _, err := s.db.Exec("TRUNCATE TABLE users")
                s.Require().NoError(err)

                // 插入测试数据
                _, err = s.db.Exec(`
                    INSERT INTO users (id, username, email, active) VALUES
                    (1, 'alice', '[email protected]', 1),
                    (2, 'bob', '[email protected]', 1),
                    (3, 'charlie', '[email protected]', 0)
                `)
                s.Require().NoError(err)
            }

            // 测试:根据ID查询用户
            func (s *UserRepositoryTestSuite) TestFindByID() {
                user, err := s.repo.FindByID(1)

                assert.NoError(s.T(), err)
                assert.NotNil(s.T(), user)
                assert.Equal(s.T(), 1, user.ID)
                assert.Equal(s.T(), "alice", user.Username)
                assert.Equal(s.T(), "[email protected]", user.Email)
                assert.True(s.T(), user.Active)
            }

            // 测试:查询不存在的用户
            func (s *UserRepositoryTestSuite) TestFindByID_NotFound() {
                user, err := s.repo.FindByID(999)

                assert.Error(s.T(), err)
                assert.Nil(s.T(), user)
                assert.Equal(s.T(), sql.ErrNoRows, err)
            }

            // 测试:查询所有激活用户
            func (s *UserRepositoryTestSuite) TestFindActiveUsers() {
                users, err := s.repo.FindActiveUsers()

                assert.NoError(s.T(), err)
                assert.Len(s.T(), users, 2)
                assert.Equal(s.T(), "alice", users[0].Username)
                assert.Equal(s.T(), "bob", users[1].Username)
            }

            // 测试:创建用户
            func (s *UserRepositoryTestSuite) TestCreate() {
                newUser := &User{
                    Username: "dave",
                    Email:    "[email protected]",
                    Active:   true,
                }

                err := s.repo.Create(newUser)
                assert.NoError(s.T(), err)
                assert.NotZero(s.T(), newUser.ID)

                // 验证数据库中确实存在
                savedUser, err := s.repo.FindByID(newUser.ID)
                assert.NoError(s.T(), err)
                assert.Equal(s.T(), "dave", savedUser.Username)
            }

            // 测试:更新用户
            func (s *UserRepositoryTestSuite) TestUpdate() {
                user, _ := s.repo.FindByID(1)
                user.Email = "[email protected]"

                err := s.repo.Update(user)
                assert.NoError(s.T(), err)

                // 验证更新成功
                updated, _ := s.repo.FindByID(1)
                assert.Equal(s.T(), "[email protected]", updated.Email)
            }

            // 测试:删除用户
            func (s *UserRepositoryTestSuite) TestDelete() {
                err := s.repo.Delete(1)
                assert.NoError(s.T(), err)

                // 验证用户已删除
                user, err := s.repo.FindByID(1)
                assert.Error(s.T(), err)
                assert.Nil(s.T(), user)
            }

            // 运行测试套件
            func TestUserRepositoryTestSuite(t *testing.T) {
                suite.Run(t, new(UserRepositoryTestSuite))
            }

            // Repository实现(简化示例)
            type UserRepository struct {
                db *sql.DB
            }

            func NewUserRepository(db *sql.DB) *UserRepository {
                return &UserRepository{db: db}
            }

            func (r *UserRepository) FindByID(id int) (*User, error) {
                user := &User{}
                err := r.db.QueryRow(
                    "SELECT id, username, email, active FROM users WHERE id = ?",
                    id,
                ).Scan(&user.ID, &user.Username, &user.Email, &user.Active)
                return user, err
            }

            func (r *UserRepository) FindActiveUsers() ([]*User, error) {
                rows, err := r.db.Query("SELECT id, username, email, active FROM users WHERE active = 1")
                if err != nil {
                    return nil, err
                }
                defer rows.Close()

                var users []*User
                for rows.Next() {
                    user := &User{}
                    rows.Scan(&user.ID, &user.Username, &user.Email, &user.Active)
                    users = append(users, user)
                }
                return users, nil
            }

            func (r *UserRepository) Create(user *User) error {
                result, err := r.db.Exec(
                    "INSERT INTO users (username, email, active) VALUES (?, ?, ?)",
                    user.Username, user.Email, user.Active,
                )
                if err != nil {
                    return err
                }
                id, _ := result.LastInsertId()
                user.ID = int(id)
                return nil
            }

            func (r *UserRepository) Update(user *User) error {
                _, err := r.db.Exec(
                    "UPDATE users SET username=?, email=?, active=? WHERE id=?",
                    user.Username, user.Email, user.Active, user.ID,
                )
                return err
            }

            func (r *UserRepository) Delete(id int) error {
                _, err := r.db.Exec("DELETE FROM users WHERE id = ?", id)
                return err
            }
            ---
    b.API集成测试
        testify的httptest工具配合标准库可以轻松测试HTTP处理器。适合测试RESTful API、Web服务接口等,验证路由、参数解析、响应格式等。

03.TDD开发场景
    a.测试驱动开发
        testify简洁的API降低了编写测试的门槛,非常适合TDD开发模式。先编写失败的测试用例,明确期望行为,再实现功能使测试通过,最后重构优化代码。
    b.红绿重构循环
        红阶段编写失败测试,绿阶段快速实现让测试通过,重构阶段优化代码质量。testify的快速反馈帮助开发者保持高效的TDD节奏。

1.5 架构设计

01.包结构设计
    a.核心包组织
        a.assert包
            assert包是testify的核心,提供非致命性断言功能。包含Assertions结构体作为断言方法的接收者,所有断言方法都返回布尔值表示断言是否通过。内部使用testing.T的Errorf方法记录失败信息,不会终止测试执行。
        b.require包
            require包提供致命性断言,与assert包API完全一致,但实现机制不同。断言失败时调用testing.T的FailNow方法立即终止测试。require包实际上是对assert包的封装,通过tHelper标记和FailNow实现致命性行为。
        c.mock包
            mock包实现完整的mock框架。核心是Mock结构体,维护期望列表ExpectedCalls和实际调用列表Calls。提供On方法设置期望、Called方法记录调用、AssertExpectations方法验证期望。使用mutex保证并发安全。
        d.suite包
            suite包提供测试套件功能。定义Suite接口和TestingSuite结构体,通过反射机制扫描测试方法、执行生命周期钩子。Run函数是套件的入口点,负责协调整个测试执行流程。
    b.包依赖关系
        ---
        // testify包依赖关系图(伪代码展示)
        package architecture

        /*
        testify包依赖结构:

        ┌─────────────────────────────────────┐
        │         testify/                     │
        │                                      │
        │  ┌──────────┐    ┌──────────┐      │
        │  │  assert  │    │ require  │      │
        │  │  (核心)  │←───│ (封装)   │      │
        │  └──────────┘    └──────────┘      │
        │       ↑                              │
        │       │                              │
        │  ┌────┴────┐    ┌──────────┐       │
        │  │  mock   │    │  suite   │       │
        │  │ (独立)  │    │ (组织)   │       │
        │  └─────────┘    └────┬─────┘       │
        │                      │               │
        │  ┌──────────┐    ┌──┴──────┐       │
        │  │   http   │    │  testing │       │
        │  │ (辅助)  │    │ (标准库) │       │
        │  └──────────┘    └──────────┘       │
        └─────────────────────────────────────┘

        核心依赖说明:
        1. assert包:核心包,依赖标准库testing和reflect
        2. require包:封装assert包,添加致命性行为
        3. mock包:独立包,不依赖assert/require
        4. suite包:依赖testing包,通过反射执行测试
        5. http包:辅助包,封装httptest功能
        */

        // 示例:包引入方式
        import (
            "testing"

            // 方式1:分别引入所需包
            "github.com/stretchr/testify/assert"
            "github.com/stretchr/testify/require"
            "github.com/stretchr/testify/mock"
            "github.com/stretchr/testify/suite"

            // 方式2:使用别名避免冲突
            tassert "github.com/stretchr/testify/assert"
            trequire "github.com/stretchr/testify/require"
        )

        // 包之间的协作示例
        func TestPackageIntegration(t *testing.T) {
            // assert和require可以混合使用
            r := require.New(t)
            a := assert.New(t)

            // require检查前置条件
            config := LoadConfig()
            r.NotNil(config, "配置必须加载成功")

            // assert检查多个属性
            a.Equal("localhost", config.Host)
            a.Equal(3306, config.Port)

            // mock对象独立使用
            mockRepo := new(MockRepository)
            mockRepo.On("Get", 1).Return("data", nil)

            // 在suite中使用assert/require/mock
            // suite.Run(t, new(IntegrationTestSuite))
        }

        type Config struct {
            Host string
            Port int
        }

        func LoadConfig() *Config {
            return &Config{Host: "localhost", Port: 3306}
        }

        type MockRepository struct {
            mock.Mock
        }

        func (m *MockRepository) Get(id int) (string, error) {
            args := m.Called(id)
            return args.String(0), args.Error(1)
        }
        ---

02.断言实现机制
    a.断言流程
        a.调用流程
            断言方法的执行流程包括:参数接收、类型检查、值比较、结果判断、错误记录。所有断言方法都遵循相同的模式,确保行为一致性和可预测性。
        b.错误记录机制
            断言失败时,testify会生成详细的错误信息,包括文件位置、行号、期望值、实际值、差异说明。使用testing.T的Helper标记确保错误堆栈指向正确的测试代码行,而不是testify内部实现。
        c.实现示例
            ---
            // 断言实现机制示例(简化版)
            package assert

            import (
                "testing"
                "fmt"
                "reflect"
            )

            // Assertions结构体:断言方法的接收者
            type Assertions struct {
                t TestingT
            }

            // TestingT接口:抽象testing.T
            type TestingT interface {
                Errorf(format string, args ...interface{})
                Helper()
            }

            // New:创建断言对象
            func New(t TestingT) *Assertions {
                return &Assertions{t: t}
            }

            // Equal断言实现(简化版)
            func (a *Assertions) Equal(expected, actual interface{}, msgAndArgs ...interface{}) bool {
                // 标记为辅助函数,错误堆栈跳过此层
                a.t.Helper()

                // 使用reflect.DeepEqual进行深度比较
                if reflect.DeepEqual(expected, actual) {
                    return true
                }

                // 断言失败,生成错误信息
                message := formatMessage(msgAndArgs...)
                diff := generateDiff(expected, actual)

                a.t.Errorf("Not equal: \n"+
                    "expected: %v\n"+
                    "actual  : %v\n"+
                    "%s\n"+
                    "%s",
                    expected, actual, diff, message)

                return false
            }

            // NoError断言实现(简化版)
            func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) bool {
                a.t.Helper()

                if err == nil {
                    return true
                }

                message := formatMessage(msgAndArgs...)
                a.t.Errorf("Received unexpected error:\n"+
                    "%+v\n"+
                    "%s",
                    err, message)

                return false
            }

            // True断言实现(简化版)
            func (a *Assertions) True(value bool, msgAndArgs ...interface{}) bool {
                a.t.Helper()

                if value {
                    return true
                }

                message := formatMessage(msgAndArgs...)
                a.t.Errorf("Should be true\n%s", message)

                return false
            }

            // 辅助函数:格式化用户消息
            func formatMessage(msgAndArgs ...interface{}) string {
                if len(msgAndArgs) == 0 {
                    return ""
                }
                if len(msgAndArgs) == 1 {
                    return fmt.Sprintf("%v", msgAndArgs[0])
                }
                return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...)
            }

            // 辅助函数:生成差异说明
            func generateDiff(expected, actual interface{}) string {
                // 实际实现会生成详细的diff信息
                return fmt.Sprintf("Diff: expected %T but got %T", expected, actual)
            }

            // 使用示例
            func TestAssertionMechanism(t *testing.T) {
                a := New(t)

                // Equal断言
                a.Equal(5, Add(2, 3), "加法结果应该正确")

                // NoError断言
                err := DoSomething()
                a.NoError(err, "不应该返回错误")

                // True断言
                a.True(IsValid(), "应该返回true")
            }

            func Add(a, b int) int { return a + b }
            func DoSomething() error { return nil }
            func IsValid() bool { return true }
            ---
    b.require vs assert区别
        a.实现差异
            require包通过在assert包基础上添加FailNow调用实现致命性。关键代码是在断言失败后调用t.FailNow(),该方法会立即终止当前goroutine的执行。
        b.对比实现
            ---
            // require vs assert实现对比
            package comparison

            import "testing"

            // assert包实现(简化)
            type AssertAssertions struct {
                t *testing.T
            }

            func (a *AssertAssertions) Equal(expected, actual interface{}) bool {
                a.t.Helper()
                if expected != actual {
                    a.t.Errorf("Not equal: expected=%v, actual=%v", expected, actual)
                    return false  // 返回false但继续执行
                }
                return true
            }

            // require包实现(简化)
            type RequireAssertions struct {
                t *testing.T
            }

            func (r *RequireAssertions) Equal(expected, actual interface{}) {
                r.t.Helper()
                if expected != actual {
                    r.t.Errorf("Not equal: expected=%v, actual=%v", expected, actual)
                    r.t.FailNow()  // 关键差异:立即终止
                }
            }

            // 行为对比测试
            func TestAssertBehavior(t *testing.T) {
                a := &AssertAssertions{t: t}

                a.Equal(1, 2)  // 失败但继续
                t.Log("这行会执行")  // 会执行
                a.Equal(3, 3)  // 继续执行后续断言
            }

            func TestRequireBehavior(t *testing.T) {
                r := &RequireAssertions{t: t}

                r.Equal(1, 2)  // 失败并终止
                t.Log("这行不会执行")  // 不会执行
            }
            ---

03.Mock框架架构
    a.核心数据结构
        a.Mock结构体
            Mock结构体是mock框架的核心,包含ExpectedCalls期望调用列表、Calls实际调用列表、mutex并发锁、testData测试数据。所有mock对象都嵌入Mock结构体,继承其方法和状态。
        b.Call结构体
            Call结构体表示一次方法调用期望,包含Method方法名、Arguments参数列表、ReturnArguments返回值、Times调用次数限制、WaitFor等待时间、Run自定义函数等属性。
        c.架构示例
            ---
            // Mock框架核心结构(简化版)
            package mock

            import (
                "sync"
                "time"
                "fmt"
            )

            // Mock核心结构体
            type Mock struct {
                // 期望调用列表
                ExpectedCalls []*Call

                // 实际调用列表
                Calls []Call

                // 并发锁
                mutex sync.Mutex

                // 测试对象
                testData TestingT
            }

            // Call表示一次方法调用期望
            type Call struct {
                Method           string        // 方法名
                Arguments        []interface{} // 期望参数
                ReturnArguments  []interface{} // 返回值
                Times            int           // 调用次数限制
                WaitFor          time.Duration // 等待时间
                RunFn            func(Arguments) // 自定义执行函数
            }

            // On:设置方法期望
            func (m *Mock) On(methodName string, arguments ...interface{}) *Call {
                m.mutex.Lock()
                defer m.mutex.Unlock()

                call := &Call{
                    Method:    methodName,
                    Arguments: arguments,
                }

                m.ExpectedCalls = append(m.ExpectedCalls, call)
                return call
            }

            // Return:设置返回值
            func (c *Call) Return(returnArguments ...interface{}) *Call {
                c.ReturnArguments = returnArguments
                return c
            }

            // Once:设置调用一次
            func (c *Call) Once() *Call {
                return c.Times(1)
            }

            // Times:设置调用次数
            func (c *Call) Times(i int) *Call {
                c.Times = i
                return c
            }

            // Called:记录实际调用
            func (m *Mock) Called(arguments ...interface{}) Arguments {
                m.mutex.Lock()
                defer m.mutex.Unlock()

                // 查找匹配的期望
                for _, expectedCall := range m.ExpectedCalls {
                    if m.matchArguments(expectedCall.Arguments, arguments) {
                        // 记录调用
                        actualCall := Call{
                            Method:    expectedCall.Method,
                            Arguments: arguments,
                        }
                        m.Calls = append(m.Calls, actualCall)

                        // 执行自定义函数
                        if expectedCall.RunFn != nil {
                            expectedCall.RunFn(Arguments(arguments))
                        }

                        // 等待指定时间
                        if expectedCall.WaitFor > 0 {
                            time.Sleep(expectedCall.WaitFor)
                        }

                        // 返回预设的返回值
                        return Arguments(expectedCall.ReturnArguments)
                    }
                }

                // 没有匹配的期望
                panic(fmt.Sprintf("unexpected call: %v", arguments))
            }

            // AssertExpectations:验证所有期望
            func (m *Mock) AssertExpectations(t TestingT) bool {
                m.mutex.Lock()
                defer m.mutex.Unlock()

                success := true

                for _, expectedCall := range m.ExpectedCalls {
                    actualCount := m.countCalls(expectedCall.Method, expectedCall.Arguments)

                    if expectedCall.Times > 0 && actualCount != expectedCall.Times {
                        t.Errorf("Expected %s to be called %d times, but was called %d times",
                            expectedCall.Method, expectedCall.Times, actualCount)
                        success = false
                    }
                }

                return success
            }

            // 辅助方法:匹配参数
            func (m *Mock) matchArguments(expected, actual []interface{}) bool {
                if len(expected) != len(actual) {
                    return false
                }

                for i := range expected {
                    // 实际实现会处理Anything等特殊匹配器
                    if expected[i] != actual[i] {
                        return false
                    }
                }

                return true
            }

            // 辅助方法:统计调用次数
            func (m *Mock) countCalls(method string, arguments []interface{}) int {
                count := 0
                for _, call := range m.Calls {
                    if call.Method == method && m.matchArguments(arguments, call.Arguments) {
                        count++
                    }
                }
                return count
            }

            type TestingT interface {
                Errorf(format string, args ...interface{})
                Helper()
            }

            type Arguments []interface{}

            func (a Arguments) Int(index int) int {
                return a[index].(int)
            }

            func (a Arguments) String(index int) string {
                return a[index].(string)
            }

            func (a Arguments) Get(index int) interface{} {
                return a[index]
            }

            func (a Arguments) Error(index int) error {
                obj := a[index]
                if obj == nil {
                    return nil
                }
                return obj.(error)
            }
            ---
    b.线程安全设计
        Mock框架使用mutex保证并发安全。所有修改ExpectedCalls和Calls列表的操作都在锁保护下进行,确保在并发测试场景下的正确性。这对于测试并发代码至关重要。

1.6 与标准库对比

01.功能对比
    a.断言能力
        a.标准库testing
            Go标准库testing包提供基础的测试框架,但不包含断言功能。开发者需要手动使用if判断和t.Errorf记录错误,代码冗长且容易出错。每个断言需要3-5行代码,错误信息需要手动格式化。
        b.testify优势
            testify提供100+断言方法,一行代码完成断言。自动生成详细的错误信息,包括期望值、实际值、类型信息和差异对比。支持深度比较、正则匹配、集合操作等复杂断言场景。
        c.对比示例
            ---
            // 断言能力对比:标准库 vs testify
            package comparison

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

            // ========== 场景1:基础相等断言 ==========

            // 标准库:需要手动判断和格式化错误
            func TestEqualStdLib(t *testing.T) {
                result := Add(2, 3)
                expected := 5

                if result != expected {
                    t.Errorf("Add(2, 3) = %d; want %d", result, expected)
                }
            }

            // testify:一行代码完成
            func TestEqualTestify(t *testing.T) {
                result := Add(2, 3)
                assert.Equal(t, 5, result)
            }

            // ========== 场景2:结构体比较 ==========

            type Person struct {
                Name string
                Age  int
            }

            // 标准库:需要逐字段比较
            func TestStructStdLib(t *testing.T) {
                expected := Person{Name: "Alice", Age: 25}
                actual := GetPerson()

                if actual.Name != expected.Name {
                    t.Errorf("Name = %s; want %s", actual.Name, expected.Name)
                }
                if actual.Age != expected.Age {
                    t.Errorf("Age = %d; want %d", actual.Age, expected.Age)
                }
            }

            // testify:自动深度比较
            func TestStructTestify(t *testing.T) {
                expected := Person{Name: "Alice", Age: 25}
                actual := GetPerson()
                assert.Equal(t, expected, actual)
            }

            // ========== 场景3:切片比较 ==========

            // 标准库:需要长度检查+元素遍历
            func TestSliceStdLib(t *testing.T) {
                expected := []int{1, 2, 3, 4, 5}
                actual := GetNumbers()

                if len(actual) != len(expected) {
                    t.Fatalf("length = %d; want %d", len(actual), len(expected))
                }

                for i := range expected {
                    if actual[i] != expected[i] {
                        t.Errorf("element[%d] = %d; want %d", i, actual[i], expected[i])
                    }
                }
            }

            // testify:一行搞定
            func TestSliceTestify(t *testing.T) {
                expected := []int{1, 2, 3, 4, 5}
                actual := GetNumbers()
                assert.Equal(t, expected, actual)
            }

            // ========== 场景4:包含判断 ==========

            // 标准库:需要手动遍历查找
            func TestContainsStdLib(t *testing.T) {
                list := []string{"apple", "banana", "cherry"}
                target := "banana"

                found := false
                for _, item := range list {
                    if item == target {
                        found = true
                        break
                    }
                }

                if !found {
                    t.Errorf("list does not contain %s", target)
                }
            }

            // testify:直接使用Contains断言
            func TestContainsTestify(t *testing.T) {
                list := []string{"apple", "banana", "cherry"}
                assert.Contains(t, list, "banana")
            }

            // ========== 场景5:正则匹配 ==========

            // 标准库:需要手动编译正则并匹配
            func TestRegexpStdLib(t *testing.T) {
                text := "[email protected]"
                pattern := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`

                matched, err := regexp.MatchString(pattern, text)
                if err != nil {
                    t.Fatalf("regexp error: %v", err)
                }
                if !matched {
                    t.Errorf("%s does not match pattern %s", text, pattern)
                }
            }

            // testify:一行正则断言
            func TestRegexpTestify(t *testing.T) {
                text := "[email protected]"
                assert.Regexp(t, `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`, text)
            }

            // ========== 场景6:错误处理 ==========

            // 标准库:需要手动判断nil
            func TestErrorStdLib(t *testing.T) {
                err := DoSomething()

                if err != nil {
                    t.Errorf("unexpected error: %v", err)
                }
            }

            // testify:专用错误断言
            func TestErrorTestify(t *testing.T) {
                err := DoSomething()
                assert.NoError(t, err)

                // 检查特定错误
                err2 := DoSomethingElse()
                assert.Error(t, err2)
                assert.EqualError(t, err2, "specific error message")
            }

            // ========== 场景7:类型检查 ==========

            // 标准库:使用reflect包
            func TestTypeStdLib(t *testing.T) {
                var obj interface{} = "hello"

                if reflect.TypeOf(obj).Kind() != reflect.String {
                    t.Errorf("type = %T; want string", obj)
                }
            }

            // testify:专用类型断言
            func TestTypeTestify(t *testing.T) {
                var obj interface{} = "hello"
                assert.IsType(t, "", obj)
            }

            // 辅助函数
            func Add(a, b int) int { return a + b }
            func GetPerson() Person { return Person{Name: "Alice", Age: 25} }
            func GetNumbers() []int { return []int{1, 2, 3, 4, 5} }
            func DoSomething() error { return nil }
            func DoSomethingElse() error { return errors.New("specific error message") }
            ---
    b.Mock能力
        a.标准库不足
            Go标准库不提供mock功能,开发者需要手动创建mock结构体、实现接口方法、记录调用信息、验证调用。这需要大量样板代码,且容易出错。
        b.testify解决方案
            testify的mock包提供完整的mock框架,通过嵌入mock.Mock结构体和调用On/Called/AssertExpectations方法,轻松实现接口mock。支持参数匹配、返回值控制、调用验证等高级功能。

02.代码量对比
    a.测试代码行数
        使用testify可以将测试代码量减少50-70%。断言代码从平均3-5行减少到1行,mock代码从几十行减少到10行以内。这不仅提高了开发效率,也增强了测试代码的可读性和可维护性。
    b.实际项目对比
        ---
        // 实际项目测试代码量对比
        package project

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

        // ========== 完整功能测试对比 ==========

        // 定义业务接口和结构
        type UserService struct {
            repo UserRepository
        }

        func (s *UserService) GetUser(id int) (*User, error) {
            return s.repo.FindByID(id)
        }

        func (s *UserService) ValidateUser(user *User) error {
            if user.Name == "" {
                return errors.New("name is required")
            }
            if user.Age < 0 || user.Age > 150 {
                return errors.New("invalid age")
            }
            return nil
        }

        type User struct {
            ID   int
            Name string
            Age  int
        }

        type UserRepository interface {
            FindByID(id int) (*User, error)
        }

        // ========== 标准库实现(约60行)==========

        // 手动实现Mock对象
        type MockUserRepositoryStdLib struct {
            calls        []MockCall
            returnValues map[int]*User
            returnErrors map[int]error
        }

        type MockCall struct {
            method string
            args   []interface{}
        }

        func (m *MockUserRepositoryStdLib) FindByID(id int) (*User, error) {
            m.calls = append(m.calls, MockCall{method: "FindByID", args: []interface{}{id}})
            if err, ok := m.returnErrors[id]; ok {
                return nil, err
            }
            if user, ok := m.returnValues[id]; ok {
                return user, nil
            }
            return nil, errors.New("not found")
        }

        func (m *MockUserRepositoryStdLib) AssertCalled(t *testing.T, method string, expectedArgs ...interface{}) {
            found := false
            for _, call := range m.calls {
                if call.method == method {
                    if len(call.args) == len(expectedArgs) {
                        match := true
                        for i := range call.args {
                            if call.args[i] != expectedArgs[i] {
                                match = false
                                break
                            }
                        }
                        if match {
                            found = true
                            break
                        }
                    }
                }
            }
            if !found {
                t.Errorf("expected method %s to be called with args %v", method, expectedArgs)
            }
        }

        // 标准库测试(约40行)
        func TestUserServiceStdLib(t *testing.T) {
            // 创建mock对象
            mockRepo := &MockUserRepositoryStdLib{
                returnValues: make(map[int]*User),
                returnErrors: make(map[int]error),
            }

            // 设置返回值
            expectedUser := &User{ID: 1, Name: "Alice", Age: 25}
            mockRepo.returnValues[1] = expectedUser

            // 创建服务
            service := &UserService{repo: mockRepo}

            // 执行测试
            user, err := service.GetUser(1)

            // 手动断言
            if err != nil {
                t.Errorf("expected no error, got %v", err)
            }
            if user == nil {
                t.Fatal("expected user, got nil")
            }
            if user.ID != expectedUser.ID {
                t.Errorf("ID = %d; want %d", user.ID, expectedUser.ID)
            }
            if user.Name != expectedUser.Name {
                t.Errorf("Name = %s; want %s", user.Name, expectedUser.Name)
            }
            if user.Age != expectedUser.Age {
                t.Errorf("Age = %d; want %d", user.Age, expectedUser.Age)
            }

            // 验证调用
            mockRepo.AssertCalled(t, "FindByID", 1)
        }

        // ========== testify实现(约20行)==========

        // testify Mock对象(嵌入mock.Mock)
        type MockUserRepositoryTestify struct {
            mock.Mock
        }

        func (m *MockUserRepositoryTestify) FindByID(id int) (*User, error) {
            args := m.Called(id)
            if args.Get(0) == nil {
                return nil, args.Error(1)
            }
            return args.Get(0).(*User), args.Error(1)
        }

        // testify测试(约15行)
        func TestUserServiceTestify(t *testing.T) {
            // 创建mock对象
            mockRepo := new(MockUserRepositoryTestify)
            expectedUser := &User{ID: 1, Name: "Alice", Age: 25}

            // 设置期望
            mockRepo.On("FindByID", 1).Return(expectedUser, nil)

            // 创建服务并测试
            service := &UserService{repo: mockRepo}
            user, err := service.GetUser(1)

            // testify断言(5行 vs 标准库15行)
            assert.NoError(t, err)
            assert.Equal(t, expectedUser, user)

            // 验证mock调用
            mockRepo.AssertExpectations(t)
        }

        // ========== 代码量统计 ==========
        /*
        标准库方案:
        - Mock实现:约50行
        - 测试代码:约40行
        - 总计:约90行

        testify方案:
        - Mock实现:约10行
        - 测试代码:约15行
        - 总计:约25行

        代码减少:约72%(90行 -> 25行)
        */
        ---

03.错误信息对比
    a.标准库输出
        标准库的错误信息由开发者手动格式化,通常只包含简单的期望值和实际值。对于复杂对象的比较,很难直观看出差异。错误位置信息有时不准确,需要手动调试定位。
    b.testify输出
        testify自动生成详细的错误信息,包括类型信息、值对比、diff差异、调用堆栈等。对于结构体比较,会展示每个字段的差异。对于切片比较,会标注不同元素的位置。错误信息格式化美观,易于阅读。
    c.输出示例
        ---
        // 错误信息对比示例
        package errorinfo

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

        type Product struct {
            ID    int
            Name  string
            Price float64
            Tags  []string
        }

        // ========== 标准库错误信息 ==========

        func TestProductStdLib(t *testing.T) {
            expected := Product{
                ID:    1,
                Name:  "Laptop",
                Price: 999.99,
                Tags:  []string{"electronics", "computer"},
            }

            actual := Product{
                ID:    1,
                Name:  "Laptop",
                Price: 1099.99,  // 价格不同
                Tags:  []string{"electronics"},  // 标签不同
            }

            // 标准库输出(不直观):
            // product_test.go:25: products are not equal
            if !reflect.DeepEqual(expected, actual) {
                t.Errorf("products are not equal")
            }

            // 即使逐字段比较,信息也不够详细:
            // product_test.go:30: Price = 1099.99; want 999.99
            if expected.Price != actual.Price {
                t.Errorf("Price = %f; want %f", actual.Price, expected.Price)
            }
        }

        // ========== testify错误信息 ==========

        func TestProductTestify(t *testing.T) {
            expected := Product{
                ID:    1,
                Name:  "Laptop",
                Price: 999.99,
                Tags:  []string{"electronics", "computer"},
            }

            actual := Product{
                ID:    1,
                Name:  "Laptop",
                Price: 1099.99,
                Tags:  []string{"electronics"},
            }

            // testify输出(详细且直观):
            /*
            Error Trace:    product_test.go:55
            Error:          Not equal:
                            expected: errorinfo.Product{
                                ID:    1,
                                Name:  "Laptop",
                                Price: 999.99,
                                Tags:  []string{"electronics", "computer"},
                            }
                            actual  : errorinfo.Product{
                                ID:    1,
                                Name:  "Laptop",
                                Price: 1099.99,
                                Tags:  []string{"electronics"},
                            }

                            Diff:
                            --- Expected
                            +++ Actual
                            @@ -2,5 +2,5 @@
                              ID: (int) 1,
                              Name: (string) (len=6) "Laptop",
                            - Price: (float64) 999.99,
                            + Price: (float64) 1099.99,
                            - Tags: ([]string) (len=2) ["electronics", "computer"]
                            + Tags: ([]string) (len=1) ["electronics"]
            Test:           TestProductTestify
            */
            assert.Equal(t, expected, actual)
        }

        // ========== 切片差异对比 ==========

        func TestSliceDiff(t *testing.T) {
            expected := []int{1, 2, 3, 4, 5}
            actual := []int{1, 2, 9, 4, 5}

            // testify清晰标注差异位置:
            /*
            Error:          Not equal:
                            expected: []int{1, 2, 3, 4, 5}
                            actual  : []int{1, 2, 9, 4, 5}

                            Diff:
                            --- Expected
                            +++ Actual
                            @@ -1,5 +1,5 @@
                             ([]int) (len=5) {
                              (int) 1,
                              (int) 2,
                            - (int) 3,
                            + (int) 9,
                              (int) 4,
                              (int) 5
            */
            assert.Equal(t, expected, actual)
        }
        ---

04.学习曲线对比
    a.标准库
        标准库testing包API简单,但需要掌握Go语言的错误处理、反射、接口等高级特性才能编写高质量测试。手动实现mock对象需要理解接口设计和依赖注入模式。初学者容易写出冗长且难以维护的测试代码。
    b.testify
        testify API丰富但学习曲线平缓。核心断言方法只有20个左右,命名直观易记。mock框架提供结构化的使用模式On-Called-Assert,容易上手。丰富的文档和示例降低了学习成本。

05.性能对比
    a.性能开销
        testify由于使用反射进行深度比较,性能略低于手写的if判断。但在测试场景下,这点性能开销完全可以忽略。测试的目标是正确性和可维护性,而非极致性能。
    b.实际影响
        在典型的测试套件中,testify的性能开销占总测试时间的比例不到1%。绝大部分测试时间消耗在业务逻辑执行、数据库操作、网络请求等方面。testify带来的开发效率提升远超过微小的性能损失。

1.7 生态系统

01.社区支持
    a.开源社区
        a.GitHub项目状态
            testify托管在GitHub上,拥有超过22000个Star,是Go语言生态中最受欢迎的测试库。项目活跃度高,每周都有代码提交,Issue响应及时,Pull Request审核专业。维护团队稳定,版本发布规律,向后兼容性好。
        b.贡献者生态
            testify拥有数百名贡献者,来自全球各地的开发者。社区欢迎各种形式的贡献,包括bug修复、新功能开发、文档改进、示例补充等。贡献指南完善,代码审查严格,确保代码质量。
        c.社区资源
            ---
            // testify社区资源索引
            package ecosystem

            /*
            ========== 官方资源 ==========

            1. GitHub仓库
               地址:https://github.com/stretchr/testify
               内容:源代码、Issue追踪、PR管理、Release发布

            2. 官方文档
               地址:https://pkg.go.dev/github.com/stretchr/testify
               内容:API文档、包说明、使用示例

            3. GoDoc文档
               地址:各包的godoc页面
               特点:自动生成、实时更新、示例完整

            ========== 社区资源 ==========

            1. Stack Overflow
               标签:testify, go-testing
               内容:问题解答、最佳实践、常见问题

            2. Reddit Go社区
               频道:r/golang
               内容:讨论、经验分享、技术交流

            3. 博客教程
               - 官方博客
               - 技术博客
               - 个人博客
               内容:深度教程、实战案例、技巧分享

            4. 视频教程
               平台:YouTube、Bilibili等
               内容:入门教程、高级技巧、实战演示

            ========== 学习路径 ==========

            入门阶段:
            1. 阅读官方README
            2. 学习基础断言(assert包)
            3. 完成简单的单元测试

            进阶阶段:
            1. 掌握require vs assert区别
            2. 学习mock基础
            3. 尝试测试套件

            高级阶段:
            1. 深入mock高级特性
            2. 掌握自定义断言
            3. 优化测试架构

            ========== 获取帮助 ==========

            1. 查看官方示例代码
            2. 搜索GitHub Issues
            3. 在Stack Overflow提问
            4. 参与社区讨论
            5. 阅读源代码实现
            */

            // 安装testify
            /*
            使用go get安装:
            go get github.com/stretchr/testify

            使用go mod管理:
            go mod init myproject
            go get github.com/stretchr/testify

            在go.mod中引用:
            require github.com/stretchr/testify v1.8.4
            */

            // 导入testify包
            import (
                "testing"

                // 断言包
                "github.com/stretchr/testify/assert"
                "github.com/stretchr/testify/require"

                // Mock包
                "github.com/stretchr/testify/mock"

                // 测试套件
                "github.com/stretchr/testify/suite"

                // HTTP测试
                "github.com/stretchr/testify/http"
            )

            // 快速上手示例
            func QuickStartExample(t *testing.T) {
                // 1. 基础断言
                assert.Equal(t, 5, Add(2, 3))

                // 2. 错误处理
                err := DoSomething()
                assert.NoError(t, err)

                // 3. 集合操作
                list := []int{1, 2, 3, 4, 5}
                assert.Contains(t, list, 3)
                assert.Len(t, list, 5)
            }

            func Add(a, b int) int { return a + b }
            func DoSomething() error { return nil }
            ---
    b.企业采用
        testify被众多知名企业和开源项目采用,包括Docker、Kubernetes、HashiCorp、Uber等。这些项目的测试代码为testify的可靠性和生产就绪性提供了有力证明。企业级项目的广泛使用也推动了testify的持续改进和功能增强。

02.扩展库和工具
    a.官方扩展
        a.http包
            testify提供http包用于HTTP测试,封装了httptest包的功能,提供更便捷的HTTP断言方法。支持测试HTTP处理器、验证响应状态码、检查响应头、比较响应体等。
        b.suite包
            suite包提供测试套件功能,支持SetUp/TearDown生命周期钩子、测试夹具管理、子测试组织等高级特性。适合构建结构化的集成测试和大型测试套件。
    b.第三方集成
        a.代码生成工具
            mockery是testify官方推荐的mock代码生成工具,可以自动为接口生成mock实现。通过分析接口定义,自动生成包含On、Called等方法的mock结构体,大幅减少手写mock代码的工作量。
        b.集成示例
            ---
            // mockery代码生成工具使用示例
            package tools

            /*
            ========== mockery安装 ==========

            使用go install安装:
            go install github.com/vektra/mockery/v2@latest

            验证安装:
            mockery --version

            ========== 使用mockery生成mock ==========

            1. 定义接口
            */

            // UserRepository接口定义
            type UserRepository interface {
                FindByID(id int) (*User, error)
                Save(user *User) error
                Delete(id int) error
                FindAll() ([]*User, error)
            }

            /*
            2. 生成mock代码

            命令行方式:
            mockery --name=UserRepository --output=mocks --outpkg=mocks

            生成的mock文件(mocks/UserRepository.go):
            */

            // 自动生成的mock代码(mockery生成)
            type MockUserRepository struct {
                mock.Mock
            }

            func (m *MockUserRepository) FindByID(id int) (*User, error) {
                args := m.Called(id)
                if args.Get(0) == nil {
                    return nil, args.Error(1)
                }
                return args.Get(0).(*User), args.Error(1)
            }

            func (m *MockUserRepository) Save(user *User) error {
                args := m.Called(user)
                return args.Error(0)
            }

            func (m *MockUserRepository) Delete(id int) error {
                args := m.Called(id)
                return args.Error(0)
            }

            func (m *MockUserRepository) FindAll() ([]*User, error) {
                args := m.Called()
                if args.Get(0) == nil {
                    return nil, args.Error(1)
                }
                return args.Get(0).([]*User), args.Error(1)
            }

            /*
            3. 使用生成的mock
            */

            func TestWithGeneratedMock(t *testing.T) {
                // 创建mock对象
                mockRepo := new(MockUserRepository)

                // 设置期望
                expectedUser := &User{ID: 1, Name: "Alice"}
                mockRepo.On("FindByID", 1).Return(expectedUser, nil)

                // 执行测试
                service := &UserService{repo: mockRepo}
                user, err := service.GetUser(1)

                // 断言
                assert.NoError(t, err)
                assert.Equal(t, expectedUser, user)

                // 验证mock
                mockRepo.AssertExpectations(t)
            }

            /*
            ========== mockery配置文件 ==========

            创建.mockery.yaml配置文件:

            with-expecter: true
            dir: "mocks"
            outpkg: "mocks"
            packages:
              github.com/myproject/repository:
                interfaces:
                  UserRepository:
                  ProductRepository:

            使用配置文件生成:
            mockery

            ========== mockery高级特性 ==========

            1. 生成带Expecter的mock
               --with-expecter
               支持类型安全的期望设置

            2. 递归生成所有接口
               --all --recursive
               扫描整个项目生成所有mock

            3. 指定输出目录
               --output=./mocks
               自定义mock文件存放位置

            4. 生成测试文件
               --testonly
               在生成的文件中添加//go:build !test
            */

            type User struct {
                ID   int
                Name string
            }

            type UserService struct {
                repo UserRepository
            }

            func (s *UserService) GetUser(id int) (*User, error) {
                return s.repo.FindByID(id)
            }
            ---
    c.CI/CD集成
        a.持续集成支持
            testify完美集成主流CI/CD平台,包括GitHub Actions、GitLab CI、Travis CI、CircleCI等。测试结果可以直接显示在CI系统中,失败的测试会阻止代码合并。支持并行测试、测试覆盖率报告、测试结果缓存等高级特性。
        b.集成配置示例
            ---
            // CI/CD集成配置示例
            package cicd

            /*
            ========== GitHub Actions配置 ==========

            文件:.github/workflows/test.yml
            */

            /*
            name: Test

            on:
              push:
                branches: [ main, develop ]
              pull_request:
                branches: [ main ]

            jobs:
              test:
                runs-on: ubuntu-latest

                steps:
                - name: Checkout code
                  uses: actions/checkout@v3

                - name: Setup Go
                  uses: actions/setup-go@v4
                  with:
                    go-version: '1.21'

                - name: Get dependencies
                  run: |
                    go mod download
                    go get github.com/stretchr/testify

                - name: Run tests
                  run: go test -v -race -coverprofile=coverage.txt -covermode=atomic ./...

                - name: Upload coverage
                  uses: codecov/codecov-action@v3
                  with:
                    file: ./coverage.txt

                - name: Run tests with testify
                  run: |
                    go test -v ./... | grep -A 10 "FAIL"
            */

            /*
            ========== GitLab CI配置 ==========

            文件:.gitlab-ci.yml
            */

            /*
            image: golang:1.21

            stages:
              - test
              - coverage

            test:
              stage: test
              script:
                - go mod download
                - go test -v -race ./...
              coverage: '/coverage: \d+.\d+% of statements/'

            coverage:
              stage: coverage
              script:
                - go test -coverprofile=coverage.out ./...
                - go tool cover -html=coverage.out -o coverage.html
              artifacts:
                paths:
                  - coverage.html
            */

            /*
            ========== Makefile测试命令 ==========

            文件:Makefile
            */

            /*
            .PHONY: test test-verbose test-coverage test-race

            # 运行所有测试
            test:
                go test ./...

            # 详细输出
            test-verbose:
                go test -v ./...

            # 生成覆盖率报告
            test-coverage:
                go test -coverprofile=coverage.out ./...
                go tool cover -html=coverage.out -o coverage.html
                @echo "Coverage report: coverage.html"

            # 竞态检测
            test-race:
                go test -race ./...

            # 运行特定测试
            test-one:
                go test -v -run $(TEST) ./...

            # 使用示例:
            # make test              # 运行所有测试
            # make test-verbose      # 详细输出
            # make test-coverage     # 生成覆盖率
            # make TEST=TestUser test-one  # 运行特定测试
            */

            /*
            ========== 测试脚本 ==========

            文件:scripts/test.sh
            */

            /*
            #!/bin/bash

            set -e

            echo "==> Running tests..."
            go test -v -race ./...

            echo "==> Generating coverage report..."
            go test -coverprofile=coverage.out ./...
            go tool cover -func=coverage.out

            echo "==> Coverage by package:"
            go test -coverprofile=coverage.out ./... | grep coverage

            echo "==> Checking test coverage threshold (80%)..."
            coverage=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//')
            threshold=80

            if (( $(echo "$coverage < $threshold" | bc -l) )); then
                echo "ERROR: Coverage $coverage% is below threshold $threshold%"
                exit 1
            fi

            echo "✓ All tests passed with $coverage% coverage"
            */
            ---

03.版本兼容性
    a.Go版本支持
        testify支持Go 1.13及以上版本,与最新的Go版本保持同步。遵循Go的兼容性承诺,不会引入破坏性变更。定期更新以支持Go语言的新特性和改进。
    b.向后兼容
        testify非常重视向后兼容性,新版本不会破坏现有测试代码。API稳定,核心功能保持不变,新功能通过新方法或包提供。版本升级平滑,风险低。
    c.版本管理
        ---
        // testify版本管理示例
        package version

        /*
        ========== go.mod版本管理 ==========

        指定版本:
        require github.com/stretchr/testify v1.8.4

        使用最新版:
        go get -u github.com/stretchr/testify

        锁定版本:
        go mod tidy

        ========== 版本历史 ==========

        v1.8.x (2023):
        - 改进错误信息显示
        - 增强mock参数匹配
        - 性能优化

        v1.7.x (2021):
        - 支持Go 1.13+
        - 新增部分断言方法
        - Bug修复

        v1.6.x (2020):
        - 稳定版本
        - 广泛使用

        ========== 升级建议 ==========

        1. 查看Release Notes
        2. 在测试环境验证
        3. 运行完整测试套件
        4. 逐步升级
        5. 监控CI结果

        ========== 依赖管理 ==========

        使用go mod管理依赖:
        go mod download
        go mod verify
        go mod tidy

        查看依赖树:
        go mod graph | grep testify

        检查更新:
        go list -m -u github.com/stretchr/testify
        */
        ---

04.替代方案对比
    a.其他测试库
        Go生态中还有其他测试库如GoConvey、Ginkgo、Gomega等。GoConvey提供BDD风格的测试和Web UI,Ginkgo/Gomega提供BDD测试框架。testify相比这些库更轻量、更贴近Go原生风格,学习成本更低,社区更大。
    b.选型建议
        对于大多数Go项目,testify是最佳选择。如果需要BDD风格测试,可以考虑Ginkgo。如果需要Web UI查看测试结果,可以考虑GoConvey。但testify的通用性、稳定性和社区支持使其成为首选。

1.8 版本特性

01.主要版本演进
    a.v1.0-v1.5早期版本
        a.核心功能建立
            早期版本建立了testify的核心架构,包括assert包、require包、mock包的基本实现。提供了基础的断言方法如Equal、NotEqual、Nil、NotNil等,以及mock框架的On、Called、AssertExpectations机制。这一阶段奠定了testify的设计理念和使用模式。
        b.社区培育
            早期版本通过简洁的API和清晰的文档快速获得社区认可,成为Go测试领域的流行选择。众多开源项目开始采用testify,形成了良好的社区生态和反馈循环。
    b.v1.6-v1.7稳定期
        a.功能完善
            v1.6和v1.7版本是testify的稳定版本,经过多年使用验证,功能成熟可靠。增加了更多断言方法,改进了错误信息显示,优化了mock框架的性能。这两个版本被大量生产环境采用,积累了丰富的实战经验。
        b.生态扩展
            这一时期出现了mockery等配套工具,Go module成为标准依赖管理方式,testify的集成和使用变得更加便捷。CI/CD集成方案成熟,测试最佳实践逐步形成。
        c.版本特性
            ---
            // v1.6-v1.7版本特性示例
            package v16v17

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

            // v1.6新增特性:Eventually和Never断言
            func TestEventuallyFeature(t *testing.T) {
                counter := 0

                // Eventually:等待条件最终满足(最多等待1秒)
                assert.Eventually(t, func() bool {
                    counter++
                    return counter >= 5
                }, time.Second, 10*time.Millisecond, "counter应该最终达到5")

                // Never:确保条件在指定时间内始终不满足
                value := 0
                assert.Never(t, func() bool {
                    return value > 10
                }, 500*time.Millisecond, 10*time.Millisecond, "value不应该超过10")
            }

            // v1.7新增特性:ErrorIs和ErrorAs断言
            func TestErrorFeatures(t *testing.T) {
                // 自定义错误类型
                var customErr = errors.New("custom error")

                // ErrorIs:检查错误链中是否包含特定错误
                wrappedErr := fmt.Errorf("wrapped: %w", customErr)
                assert.ErrorIs(t, wrappedErr, customErr)

                // ErrorAs:检查错误是否可以转换为特定类型
                type MyError struct {
                    Code    int
                    Message string
                }

                func (e *MyError) Error() string {
                    return e.Message
                }

                myErr := &MyError{Code: 404, Message: "not found"}
                wrappedMyErr := fmt.Errorf("failed: %w", myErr)

                var target *MyError
                assert.ErrorAs(t, wrappedMyErr, &target)
                assert.Equal(t, 404, target.Code)
            }

            // v1.7改进:mock返回函数支持
            func TestMockReturnFunc(t *testing.T) {
                mockRepo := new(MockRepository)

                // Return函数:根据参数动态返回不同值
                mockRepo.On("Get", mock.Anything).Return(func(id int) string {
                    return fmt.Sprintf("item-%d", id)
                }, nil)

                result1, _ := mockRepo.Get(1)
                assert.Equal(t, "item-1", result1)

                result2, _ := mockRepo.Get(2)
                assert.Equal(t, "item-2", result2)

                mockRepo.AssertExpectations(t)
            }

            type MockRepository struct {
                mock.Mock
            }

            func (m *MockRepository) Get(id int) (string, error) {
                args := m.Called(id)
                if fn, ok := args.Get(0).(func(int) string); ok {
                    return fn(id), args.Error(1)
                }
                return args.String(0), args.Error(1)
            }
            ---
    c.v1.8当前版本
        a.现代化改进
            v1.8是当前主流使用的版本,支持Go 1.13及以上版本。改进了错误信息的格式化输出,增强了diff显示,优化了大型对象的比较性能。增加了更多实用的断言方法,改进了mock框架的类型安全性。
        b.Go生态适配
            完全支持Go modules,与Go 1.18+的泛型特性兼容。改进了与Go标准库的集成,特别是testing.T和testing.B的支持。优化了在并发测试场景下的表现,修复了竞态条件问题。
        c.核心改进
            ---
            // v1.8版本核心改进示例
            package v18

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

            // v1.8改进:更好的diff显示
            func TestImprovedDiff(t *testing.T) {
                type ComplexStruct struct {
                    ID        int
                    Name      string
                    Tags      []string
                    Metadata  map[string]interface{}
                    CreatedAt time.Time
                }

                expected := ComplexStruct{
                    ID:   1,
                    Name: "Test",
                    Tags: []string{"tag1", "tag2", "tag3"},
                    Metadata: map[string]interface{}{
                        "key1": "value1",
                        "key2": 123,
                    },
                    CreatedAt: time.Now(),
                }

                actual := ComplexStruct{
                    ID:   1,
                    Name: "Test",
                    Tags: []string{"tag1", "tag2"},  // 少一个tag
                    Metadata: map[string]interface{}{
                        "key1": "value1",
                        "key2": 456,  // 值不同
                    },
                    CreatedAt: expected.CreatedAt,
                }

                // v1.8的diff显示更加清晰,精确指出差异位置
                assert.Equal(t, expected, actual)
            }

            // v1.8新增:Greater/Less系列断言
            func TestComparisonAssertions(t *testing.T) {
                // Greater:大于
                assert.Greater(t, 10, 5)

                // GreaterOrEqual:大于等于
                assert.GreaterOrEqual(t, 10, 10)

                // Less:小于
                assert.Less(t, 5, 10)

                // LessOrEqual:小于等于
                assert.LessOrEqual(t, 5, 5)

                // Positive:正数
                assert.Positive(t, 42)

                // Negative:负数
                assert.Negative(t, -42)
            }

            // v1.8改进:mock参数匹配增强
            func TestEnhancedMockMatching(t *testing.T) {
                mockService := new(MockService)

                // MatchedBy:自定义匹配函数
                mockService.On("Process", mock.MatchedBy(func(user *User) bool {
                    return user.Age >= 18 && user.Active
                })).Return(nil)

                // 只有满足条件的调用才会匹配
                err1 := mockService.Process(&User{Age: 20, Active: true})
                assert.NoError(t, err1)

                // 不满足条件的调用会失败
                // err2 := mockService.Process(&User{Age: 16, Active: true})
                // 会panic: no matching expectation

                mockService.AssertExpectations(t)
            }

            // v1.8新增:NotImplements断言
            func TestImplementsAssertions(t *testing.T) {
                type Reader interface {
                    Read(p []byte) (n int, err error)
                }

                type Writer interface {
                    Write(p []byte) (n int, err error)
                }

                var buffer bytes.Buffer

                // Implements:检查是否实现接口
                assert.Implements(t, (*Writer)(nil), &buffer)
                assert.Implements(t, (*Reader)(nil), &buffer)

                // NotImplements:检查是否未实现接口
                type MyStruct struct{}
                assert.NotImplements(t, (*Writer)(nil), &MyStruct{})
            }

            type User struct {
                Age    int
                Active bool
            }

            type MockService struct {
                mock.Mock
            }

            func (m *MockService) Process(user *User) error {
                args := m.Called(user)
                return args.Error(0)
            }
            ---

02.功能特性对比
    a.断言方法演进
        a.基础断言增强
            从最初的20多个断言方法增加到100+个方法。早期只有Equal、NotEqual、Nil等基础断言,后续版本不断增加Contains、ElementsMatch、Subset、Greater、Less等实用断言。每个版本都在保持向后兼容的前提下丰富断言能力。
        b.错误处理改进
            错误相关断言从简单的Error、NoError扩展到ErrorIs、ErrorAs、EqualError、ErrorContains等。适应Go 1.13+的错误包装机制,支持错误链检查和类型断言。错误信息显示也不断优化,提供更详细的诊断信息。
    b.Mock功能增强
        a.参数匹配进化
            从简单的精确匹配发展到支持Anything、AnythingOfType、MatchedBy等灵活匹配器。增加了IsType、AnythingOfTypeArgument等类型检查匹配器。支持自定义匹配函数,可以实现复杂的参数验证逻辑。
        b.调用控制完善
            从基础的On-Return机制扩展到支持Once、Twice、Times、Maybe等调用次数控制。增加了Run自定义执行函数、WaitUntil等待控制。支持After、NotBefore等调用顺序验证。
        c.演进示例
            ---
            // Mock功能演进对比
            package evolution

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

            // ========== 早期版本:基础Mock ==========

            func TestBasicMockOldStyle(t *testing.T) {
                mockRepo := new(MockUserRepository)

                // 早期:只支持精确参数匹配和简单返回
                mockRepo.On("Get", 1).Return(&User{ID: 1, Name: "Alice"}, nil)

                user, err := mockRepo.Get(1)
                assert.NoError(t, err)
                assert.Equal(t, "Alice", user.Name)

                mockRepo.AssertExpectations(t)
            }

            // ========== 现代版本:高级Mock ==========

            func TestAdvancedMockNewStyle(t *testing.T) {
                mockRepo := new(MockUserRepository)

                // 现代:支持灵活匹配、调用控制、自定义行为
                mockRepo.On("Get", mock.MatchedBy(func(id int) bool {
                    return id > 0 && id < 1000
                })).Run(func(args mock.Arguments) {
                    id := args.Int(0)
                    t.Logf("Get called with id=%d", id)
                }).Return(func(id int) *User {
                    return &User{ID: id, Name: fmt.Sprintf("User-%d", id)}
                }, nil).Times(3)

                // 调用3次,每次返回不同用户
                for i := 1; i <= 3; i++ {
                    user, err := mockRepo.Get(i)
                    assert.NoError(t, err)
                    assert.Equal(t, i, user.ID)
                }

                // 验证调用次数
                mockRepo.AssertExpectations(t)
                mockRepo.AssertNumberOfCalls(t, "Get", 3)
            }

            // ========== Mock链式调用增强 ==========

            func TestMockChaining(t *testing.T) {
                mockRepo := new(MockUserRepository)

                // 链式设置多个期望
                mockRepo.On("Get", 1).Return(&User{ID: 1}, nil).Once()
                mockRepo.On("Get", 2).Return(&User{ID: 2}, nil).Once()
                mockRepo.On("Get", mock.Anything).Return(nil, errors.New("not found")).Maybe()

                // 第一次调用
                user1, _ := mockRepo.Get(1)
                assert.Equal(t, 1, user1.ID)

                // 第二次调用
                user2, _ := mockRepo.Get(2)
                assert.Equal(t, 2, user2.ID)

                // 第三次调用匹配Anything
                user3, err := mockRepo.Get(999)
                assert.Error(t, err)
                assert.Nil(t, user3)

                mockRepo.AssertExpectations(t)
            }

            type MockUserRepository struct {
                mock.Mock
            }

            func (m *MockUserRepository) Get(id int) (*User, error) {
                args := m.Called(id)
                if args.Get(0) == nil {
                    return nil, args.Error(1)
                }
                if fn, ok := args.Get(0).(func(int) *User); ok {
                    return fn(id), args.Error(1)
                }
                return args.Get(0).(*User), args.Error(1)
            }
            ---

03.性能优化历程
    a.早期性能
        早期版本主要关注功能完整性和API易用性,性能优化不是首要目标。反射操作较多,大型对象比较时性能开销明显。但对于常规测试场景,性能已经足够。
    b.持续优化
        后续版本不断优化性能,减少不必要的反射调用,改进对象比较算法。增加缓存机制,避免重复的类型检查。优化并发场景下的锁竞争,提高并行测试性能。
    c.性能基准
        ---
        // testify性能优化对比基准测试
        package benchmark

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

        // 基准测试:简单断言性能
        func BenchmarkSimpleAssertion(b *testing.B) {
            for i := 0; i < b.N; i++ {
                assert.Equal(b, 5, 2+3)
            }
        }

        // 基准测试:结构体比较性能
        func BenchmarkStructAssertion(b *testing.B) {
            user1 := User{ID: 1, Name: "Alice", Age: 25}
            user2 := User{ID: 1, Name: "Alice", Age: 25}

            b.ResetTimer()
            for i := 0; i < b.N; i++ {
                assert.Equal(b, user1, user2)
            }
        }

        // 基准测试:切片比较性能
        func BenchmarkSliceAssertion(b *testing.B) {
            slice1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
            slice2 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

            b.ResetTimer()
            for i := 0; i < b.N; i++ {
                assert.Equal(b, slice1, slice2)
            }
        }

        // 基准测试:mock调用性能
        func BenchmarkMockCall(b *testing.B) {
            mockRepo := new(MockUserRepository)
            mockRepo.On("Get", mock.Anything).Return(&User{ID: 1}, nil)

            b.ResetTimer()
            for i := 0; i < b.N; i++ {
                mockRepo.Get(1)
            }
        }

        /*
        性能测试结果(参考值):
        BenchmarkSimpleAssertion-8      20000000    85 ns/op
        BenchmarkStructAssertion-8       5000000   320 ns/op
        BenchmarkSliceAssertion-8        3000000   450 ns/op
        BenchmarkMockCall-8              2000000   680 ns/op

        结论:
        1. 简单断言性能优秀,纳秒级延迟
        2. 复杂对象比较有一定开销,但仍在可接受范围
        3. Mock调用性能良好,适合大规模测试
        4. 总体性能足以支撑生产级测试需求
        */
        ---

04.未来发展方向
    a.Go泛型支持
        随着Go 1.18引入泛型,testify未来可能提供类型安全的泛型断言方法。例如Equal[T](expected T, actual T),在编译时检查类型匹配。这将进一步提升API的类型安全性和IDE支持。
    b.性能持续优化
        继续优化反射性能,探索编译时代码生成方案。改进大型对象比较算法,减少内存分配。优化并发测试场景的性能表现。
    c.生态系统扩展
        加强与其他测试工具的集成,如性能测试、模糊测试等。改进mock代码生成工具,支持更多场景。增强CI/CD集成能力,提供更丰富的测试报告。
    d.社区驱动创新
        testify作为开源项目,未来发展方向将由社区需求驱动。鼓励社区贡献新功能、改进建议和最佳实践。保持API稳定性的同时,持续创新满足新的测试需求。

2 核心组件

2.1 汇总:4个

01.核心包概览
    a.包组织结构
        testify采用模块化设计,将功能划分为多个独立的包,每个包专注于特定的测试场景。核心包包括assert断言包、require断言包、mock模拟包、suite套件包、http测试包。这种设计使开发者可以按需引入,保持代码简洁。
    b.包依赖关系
        assert包是基础,require包在assert基础上添加致命性行为。mock包独立于断言包,提供完整的mock功能。suite包依赖testing标准库,通过反射机制管理测试生命周期。http包封装httptest,简化HTTP测试。各包职责清晰,耦合度低。
    c.包导入示例
        ---
        // testify核心包导入和使用示例
        package overview

        import (
            "testing"

            // 1. assert包:非致命性断言
            "github.com/stretchr/testify/assert"

            // 2. require包:致命性断言
            "github.com/stretchr/testify/require"

            // 3. mock包:模拟对象
            "github.com/stretchr/testify/mock"

            // 4. suite包:测试套件
            "github.com/stretchr/testify/suite"

            // 5. http包:HTTP测试(可选)
            "github.com/stretchr/testify/http"
        )

        // ========== 包使用场景示例 ==========

        // 场景1:使用assert包进行基础测试
        func TestWithAssert(t *testing.T) {
            // 创建断言对象
            a := assert.New(t)

            // 执行多个断言,即使某个失败也继续执行
            a.Equal(5, Add(2, 3), "加法测试")
            a.NotZero(10, "非零测试")
            a.True(IsValid(), "布尔测试")

            // 所有断言都会执行,最后汇总报告失败
        }

        // 场景2:使用require包检查前置条件
        func TestWithRequire(t *testing.T) {
            r := require.New(t)

            // 检查前置条件,失败则立即终止
            config := LoadConfig()
            r.NotNil(config, "配置必须加载成功")

            // 如果config为nil,这里的代码不会执行
            r.Equal("localhost", config.Host)
            r.Equal(3306, config.Port)
        }

        // 场景3:使用mock包创建模拟对象
        func TestWithMock(t *testing.T) {
            // 创建mock对象
            mockRepo := new(MockRepository)

            // 设置期望
            mockRepo.On("Get", 1).Return("data", nil)

            // 执行测试
            result, err := mockRepo.Get(1)

            // 断言结果
            assert.NoError(t, err)
            assert.Equal(t, "data", result)

            // 验证mock期望
            mockRepo.AssertExpectations(t)
        }

        // 场景4:使用suite包组织测试
        type MyTestSuite struct {
            suite.Suite
            db   *Database
            repo *Repository
        }

        func (s *MyTestSuite) SetupSuite() {
            s.db = ConnectDatabase()
        }

        func (s *MyTestSuite) SetupTest() {
            s.repo = NewRepository(s.db)
        }

        func (s *MyTestSuite) TestSomething() {
            result := s.repo.Query()
            assert.NotNil(s.T(), result)
        }

        func (s *MyTestSuite) TearDownSuite() {
            s.db.Close()
        }

        func TestMyTestSuite(t *testing.T) {
            suite.Run(t, new(MyTestSuite))
        }

        // ========== 包组合使用示例 ==========

        // 实际项目中通常组合使用多个包
        func TestCombinedUsage(t *testing.T) {
            // 1. require检查前置条件
            r := require.New(t)
            db := ConnectDatabase()
            r.NotNil(db, "数据库连接必须成功")
            defer db.Close()

            // 2. mock创建依赖
            mockCache := new(MockCache)
            mockCache.On("Get", "key1").Return("value1", nil)

            // 3. assert验证结果
            a := assert.New(t)
            service := NewService(db, mockCache)
            result, err := service.Process("key1")

            a.NoError(err)
            a.Equal("value1", result)

            // 4. 验证mock调用
            mockCache.AssertExpectations(t)
        }

        // ========== 辅助类型定义 ==========

        type MockRepository struct {
            mock.Mock
        }

        func (m *MockRepository) Get(id int) (string, error) {
            args := m.Called(id)
            return args.String(0), args.Error(1)
        }

        type MockCache struct {
            mock.Mock
        }

        func (m *MockCache) Get(key string) (string, error) {
            args := m.Called(key)
            return args.String(0), args.Error(1)
        }

        type Config struct {
            Host string
            Port int
        }

        type Database struct{}
        type Repository struct{}
        type Service struct{}

        func Add(a, b int) int { return a + b }
        func IsValid() bool { return true }
        func LoadConfig() *Config { return &Config{Host: "localhost", Port: 3306} }
        func ConnectDatabase() *Database { return &Database{} }
        func (db *Database) Close() {}
        func NewRepository(db *Database) *Repository { return &Repository{} }
        func (r *Repository) Query() interface{} { return "result" }
        func NewService(db *Database, cache *MockCache) *Service { return &Service{} }
        func (s *Service) Process(key string) (string, error) { return "value1", nil }
        ---

02.功能定位对比
    a.assert vs require
        a.核心差异
            assert包提供非致命性断言,断言失败时记录错误但继续执行。适用于需要检查多个条件的场景,一次性发现所有问题。require包提供致命性断言,断言失败时立即终止测试。适用于前置条件检查,避免在错误状态下继续执行导致panic或误导性错误。
        b.选择建议
            前置条件使用require,如数据库连接、配置加载、必需资源初始化。多个独立检查使用assert,如验证对象的多个字段、检查集合中的多个元素。可以在同一测试中混合使用两种断言,先用require检查前置条件,再用assert验证多个结果。
        c.对比示例
            ---
            // assert vs require选择示例
            package comparison

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

            // ========== 错误用法示例 ==========

            // 错误1:前置条件使用assert(可能导致panic)
            func TestBadAssertUsage(t *testing.T) {
                a := assert.New(t)

                // 如果db为nil,断言失败但继续执行
                db := ConnectDatabase()
                a.NotNil(db)  // ❌ 错误:应该使用require

                // db可能为nil,这里会panic!
                db.Query("SELECT * FROM users")
            }

            // 错误2:多个独立检查使用require(只能发现第一个问题)
            func TestBadRequireUsage(t *testing.T) {
                r := require.New(t)

                user := GetUser(1)
                r.NotNil(user)

                // 如果Name错误,后续检查不会执行
                r.Equal("Alice", user.Name)  // ❌ 第一个失败就终止
                r.Equal(25, user.Age)        // 看不到这个问题
                r.True(user.Active)          // 看不到这个问题
            }

            // ========== 正确用法示例 ==========

            // 正确1:前置条件使用require
            func TestCorrectRequireUsage(t *testing.T) {
                r := require.New(t)
                a := assert.New(t)

                // require检查前置条件
                db := ConnectDatabase()
                r.NotNil(db, "数据库连接必须成功")
                defer db.Close()

                // 确保db不为nil后,安全地使用
                rows, err := db.Query("SELECT * FROM users")
                r.NoError(err, "查询必须成功")
                defer rows.Close()

                // assert检查多个结果
                a.True(rows.Next(), "至少有一条记录")
                a.NotNil(rows, "结果集不为空")
            }

            // 正确2:多个独立检查使用assert
            func TestCorrectAssertUsage(t *testing.T) {
                r := require.New(t)
                a := assert.New(t)

                // require检查对象存在
                user := GetUser(1)
                r.NotNil(user, "用户必须存在")

                // assert检查所有字段,一次性发现所有问题
                a.Equal("Alice", user.Name, "用户名检查")
                a.Equal(25, user.Age, "年龄检查")
                a.True(user.Active, "激活状态检查")
                a.NotEmpty(user.Email, "邮箱检查")
                // 即使某些断言失败,所有字段都会被检查
            }

            // 正确3:嵌套资源的正确处理
            func TestNestedResourceHandling(t *testing.T) {
                r := require.New(t)
                a := assert.New(t)

                // 第一层:数据库连接
                db := ConnectDatabase()
                r.NotNil(db)
                defer db.Close()

                // 第二层:事务
                tx, err := db.Begin()
                r.NoError(err)
                defer tx.Rollback()

                // 第三层:执行操作
                result, err := tx.Exec("INSERT INTO users VALUES (?, ?)", 1, "Alice")
                r.NoError(err)

                // 验证多个结果(使用assert)
                affected, _ := result.RowsAffected()
                a.Equal(int64(1), affected)

                lastID, _ := result.LastInsertId()
                a.Positive(lastID)
            }

            // ========== 实战场景 ==========

            // 场景:HTTP处理器测试
            func TestHTTPHandler(t *testing.T) {
                r := require.New(t)
                a := assert.New(t)

                // require:确保请求创建成功
                req, err := http.NewRequest("GET", "/users/1", nil)
                r.NoError(err)
                r.NotNil(req)

                // 执行请求
                rr := httptest.NewRecorder()
                handler := UserHandler{}
                handler.ServeHTTP(rr, req)

                // assert:检查多个响应属性
                a.Equal(http.StatusOK, rr.Code, "状态码检查")
                a.Contains(rr.Header().Get("Content-Type"), "application/json", "Content-Type检查")
                a.NotEmpty(rr.Body.String(), "响应体检查")
                a.JSONEq(`{"id":1,"name":"Alice"}`, rr.Body.String(), "JSON内容检查")
            }

            type User struct {
                Name   string
                Age    int
                Active bool
                Email  string
            }

            type UserHandler struct{}

            func (h UserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
                w.Header().Set("Content-Type", "application/json")
                w.WriteHeader(http.StatusOK)
                w.Write([]byte(`{"id":1,"name":"Alice"}`))
            }

            func GetUser(id int) *User {
                return &User{Name: "Alice", Age: 25, Active: true, Email: "[email protected]"}
            }
            ---
    b.mock vs 手写mock
        mock包提供结构化的mock框架,通过On-Called-Assert模式实现期望设置和验证。相比手写mock对象,代码量减少80%以上,且支持参数匹配、调用次数验证等高级功能。手写mock适合简单场景,mock包适合复杂依赖和精确验证。
    c.suite vs 函数式测试
        suite包将测试组织成结构体,支持SetUp/TearDown钩子和共享状态。适合需要复杂初始化、资源管理、多个相关测试的场景。传统函数式测试简单直接,适合独立的单元测试。suite增加了组织性,但也引入了一定复杂度。

03.使用频率统计
    a.包使用优先级
        assert包是最常用的包,几乎所有测试都会使用。require包在需要前置条件检查时使用,使用频率约为assert的30-40%。mock包在需要隔离依赖时使用,使用频率约20-30%。suite包在大型项目和集成测试中使用,使用频率约10-15%。http包在Web项目中使用,使用频率约5-10%。
    b.项目类型差异
        纯逻辑库项目主要使用assert和require包,mock使用较少。微服务项目大量使用mock包隔离外部依赖,suite包组织集成测试。Web应用项目广泛使用http包测试API,配合mock和suite。数据密集型项目使用suite管理数据库连接和测试数据。
    c.学习路径建议
        ---
        // testify学习路径建议
        package learning

        /*
        ========== 第一阶段:基础入门(1-2天)==========

        目标:掌握基础断言,能编写简单单元测试

        学习内容:
        1. assert包基础
           - Equal, NotEqual
           - Nil, NotNil
           - True, False
           - NoError, Error

        2. 基本测试编写
           - 创建断言对象
           - 编写测试函数
           - 运行测试

        实践项目:
        - 为简单函数编写测试
        - 测试数据结构操作
        - 测试错误处理

        ========== 第二阶段:进阶应用(3-5天)==========

        目标:掌握require、mock基础,能测试复杂业务逻辑

        学习内容:
        1. require包
           - 理解致命性断言
           - 前置条件检查
           - 资源管理

        2. mock包基础
           - 创建mock对象
           - On-Return模式
           - AssertExpectations验证

        3. 更多断言方法
           - Contains, ElementsMatch
           - Greater, Less
           - Regexp, JSONEq

        实践项目:
        - 测试服务层代码
        - mock数据库接口
        - 测试业务规则

        ========== 第三阶段:高级特性(5-7天)==========

        目标:掌握suite、高级mock,能构建完整测试体系

        学习内容:
        1. suite包
           - 测试套件组织
           - 生命周期钩子
           - 测试夹具管理

        2. mock高级特性
           - 参数匹配器
           - 调用次数控制
           - 自定义行为

        3. http包
           - HTTP处理器测试
           - API集成测试

        实践项目:
        - 构建集成测试套件
        - 复杂mock场景
        - API端到端测试

        ========== 第四阶段:最佳实践(持续)==========

        目标:形成测试思维,建立团队测试规范

        学习内容:
        1. 测试组织架构
        2. CI/CD集成
        3. 测试覆盖率管理
        4. 性能测试
        5. 测试重构技巧

        实践项目:
        - 真实项目测试
        - 团队规范建立
        - 测试自动化
        */
        ---

04.包版本兼容性
    a.稳定性保证
        testify所有核心包保持API稳定,向后兼容性好。新版本不会破坏现有代码,新功能通过新方法提供。包之间版本同步,统一发布,避免版本冲突。
    b.依赖管理
        使用Go modules统一管理所有包的依赖。一次引入testify,所有包使用相同版本。go.mod自动处理版本锁定和依赖解析,确保构建可重现性。

2.2 assert包

01.包设计理念
    a.非致命性断言
        assert包的核心设计理念是非致命性断言,断言失败时记录错误但不终止测试执行。这使得一个测试函数可以执行多个断言,一次性发现所有问题,而不是每次只能看到第一个失败的断言。这种设计提高了测试的诊断效率。
    b.链式API设计
        assert包提供两种使用方式:直接调用包级别函数assert.Equal(t, expected, actual),或创建断言对象a := assert.New(t)然后调用a.Equal(expected, actual)。后者避免了重复传递testing.T参数,代码更简洁。
    c.详细错误信息
        断言失败时自动生成详细的错误信息,包括期望值、实际值、类型信息、差异对比等。对于复杂对象,提供格式化的diff输出,清晰标注差异位置。支持自定义错误消息,进一步增强可读性。
    d.设计示例
        ---
        // assert包设计理念示例
        package assertdesign

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

        // ========== 使用方式1:包级别函数 ==========

        func TestPackageLevel(t *testing.T) {
            // 每次调用都传递t参数
            assert.Equal(t, 5, Add(2, 3))
            assert.NotNil(t, GetUser(1))
            assert.True(t, IsValid())
            assert.NoError(t, DoSomething())

            // 优点:直观简单
            // 缺点:需要重复传递t
        }

        // ========== 使用方式2:断言对象 ==========

        func TestAssertionObject(t *testing.T) {
            // 创建断言对象,只传递一次t
            a := assert.New(t)

            // 后续调用不需要传递t
            a.Equal(5, Add(2, 3))
            a.NotNil(GetUser(1))
            a.True(IsValid())
            a.NoError(DoSomething())

            // 优点:代码简洁
            // 缺点:需要额外一行创建对象
        }

        // ========== 非致命性演示 ==========

        func TestNonFatal(t *testing.T) {
            a := assert.New(t)

            user := GetUser(1)

            // 即使某个断言失败,所有断言都会执行
            a.NotNil(user)                    // 断言1
            a.Equal("Alice", user.Name)       // 断言2:即使断言1失败,这个也会执行
            a.Equal(25, user.Age)             // 断言3:继续执行
            a.True(user.Active)               // 断言4:继续执行
            a.NotEmpty(user.Email)            // 断言5:继续执行

            // 测试结束后会看到所有失败的断言,而不是只看到第一个
            t.Log("测试继续执行到这里")
        }

        // ========== 错误信息演示 ==========

        func TestDetailedErrorInfo(t *testing.T) {
            a := assert.New(t)

            type Product struct {
                ID    int
                Name  string
                Price float64
                Tags  []string
            }

            expected := Product{
                ID:    1,
                Name:  "Laptop",
                Price: 999.99,
                Tags:  []string{"electronics", "computer"},
            }

            actual := Product{
                ID:    1,
                Name:  "Laptop",
                Price: 1099.99,  // 不同
                Tags:  []string{"electronics"},  // 不同
            }

            // assert会生成详细的diff信息
            a.Equal(expected, actual, "产品信息应该匹配")

            /*
            输出示例:
            Error Trace:    test.go:65
            Error:          Not equal:
                            expected: Product{ID:1, Name:"Laptop", Price:999.99, Tags:["electronics", "computer"]}
                            actual  : Product{ID:1, Name:"Laptop", Price:1099.99, Tags:["electronics"]}

                            Diff:
                            --- Expected
                            +++ Actual
                            @@ -2,5 +2,5 @@
                              ID: 1,
                              Name: "Laptop",
                            - Price: 999.99,
                            + Price: 1099.99,
                            - Tags: ["electronics", "computer"]
                            + Tags: ["electronics"]
            Messages:       产品信息应该匹配
            Test:           TestDetailedErrorInfo
            */
        }

        // ========== 自定义错误消息 ==========

        func TestCustomMessages(t *testing.T) {
            a := assert.New(t)

            // 不带消息
            a.Equal(5, Add(2, 3))

            // 简单字符串消息
            a.Equal(10, Add(5, 5), "5+5应该等于10")

            // 格式化消息
            x, y := 3, 4
            a.Equal(7, Add(x, y), "Add(%d, %d)应该等于7", x, y)

            // 复杂消息
            user := GetUser(1)
            a.NotNil(user, "用户ID=%d应该存在,当前系统状态=%s", 1, GetSystemStatus())
        }

        // 辅助函数
        type User struct {
            Name   string
            Age    int
            Active bool
            Email  string
        }

        func Add(a, b int) int { return a + b }
        func GetUser(id int) *User { return &User{Name: "Alice", Age: 25, Active: true, Email: "[email protected]"} }
        func IsValid() bool { return true }
        func DoSomething() error { return nil }
        func GetSystemStatus() string { return "running" }
        ---

02.核心断言方法
    a.相等性断言
        a.Equal系列
            Equal是最常用的断言方法,使用reflect.DeepEqual进行深度比较,支持所有Go类型包括结构体、切片、map等。NotEqual检查不相等。Same检查指针相同,NotSame检查指针不同。
        b.使用示例
            ---
            // 相等性断言示例
            package equality

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

            func TestEqualityAssertions(t *testing.T) {
                a := assert.New(t)

                // ========== Equal:深度相等 ==========

                // 基本类型
                a.Equal(5, 2+3)
                a.Equal("hello", "hel"+"lo")
                a.Equal(3.14, 3.14)

                // 结构体
                type Point struct{ X, Y int }
                a.Equal(Point{1, 2}, Point{1, 2})

                // 切片
                a.Equal([]int{1, 2, 3}, []int{1, 2, 3})

                // map
                a.Equal(
                    map[string]int{"a": 1, "b": 2},
                    map[string]int{"a": 1, "b": 2},
                )

                // 指针(比较指向的值,不是指针地址)
                x, y := 42, 42
                a.Equal(&x, &y)  // 通过:值相等

                // ========== NotEqual:不相等 ==========

                a.NotEqual(5, 6)
                a.NotEqual("hello", "world")
                a.NotEqual([]int{1, 2}, []int{1, 3})

                // ========== Same:指针相同 ==========

                obj1 := &Point{1, 2}
                obj2 := obj1
                obj3 := &Point{1, 2}

                a.Same(obj1, obj2)     // 通过:指向同一对象
                a.NotSame(obj1, obj3)  // 通过:不同对象(虽然值相等)

                // ========== EqualValues:类型转换后相等 ==========

                // Equal会严格检查类型,EqualValues允许类型转换
                var i int = 5
                var i32 int32 = 5

                // a.Equal(i, i32)         // 失败:类型不同
                a.EqualValues(i, i32)   // 通过:值相等

                // ========== InDelta:浮点数近似相等 ==========

                // 浮点数精度问题
                a.InDelta(0.1+0.2, 0.3, 0.0001)  // 允许误差

                // InDeltaSlice:切片中每个元素都近似相等
                a.InDeltaSlice(
                    []float64{0.1, 0.2, 0.3},
                    []float64{0.10001, 0.20001, 0.30001},
                    0.001,
                )

                // InDeltaMapValues:map中每个值都近似相等
                a.InDeltaMapValues(
                    map[string]float64{"a": 0.1, "b": 0.2},
                    map[string]float64{"a": 0.10001, "b": 0.20001},
                    0.001,
                )

                // InEpsilon:相对误差检查
                a.InEpsilon(1000, 1001, 0.01)  // 允许1%误差
            }
            ---
    b.存在性断言
        a.Nil系列
            Nil检查值为nil,NotNil检查值不为nil。支持指针、接口、slice、map、channel、function等可为nil的类型。Zero检查零值,NotZero检查非零值,适用于所有类型。Empty检查空集合或零值,NotEmpty检查非空。
        b.使用示例
            ---
            // 存在性断言示例
            package existence

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

            func TestExistenceAssertions(t *testing.T) {
                a := assert.New(t)

                // ========== Nil / NotNil ==========

                // 指针
                var ptr *int = nil
                a.Nil(ptr)

                x := 42
                a.NotNil(&x)

                // 接口
                var err error = nil
                a.Nil(err)

                err = errors.New("error")
                a.NotNil(err)

                // slice
                var s []int = nil
                a.Nil(s)

                s = []int{}
                a.NotNil(s)  // 注意:空切片不是nil

                // map
                var m map[string]int = nil
                a.Nil(m)

                m = make(map[string]int)
                a.NotNil(m)

                // channel
                var ch chan int = nil
                a.Nil(ch)

                ch = make(chan int)
                a.NotNil(ch)

                // ========== Zero / NotZero ==========

                // 零值检查(适用于所有类型)
                a.Zero(0)
                a.Zero("")
                a.Zero(false)
                a.Zero(0.0)

                // 非零值
                a.NotZero(1)
                a.NotZero("hello")
                a.NotZero(true)
                a.NotZero(3.14)

                // 结构体零值
                type Point struct{ X, Y int }
                a.Zero(Point{})
                a.NotZero(Point{X: 1})

                // ========== Empty / NotEmpty ==========

                // 字符串
                a.Empty("")
                a.NotEmpty("hello")

                // 切片
                a.Empty([]int{})
                a.NotEmpty([]int{1, 2, 3})

                // map
                a.Empty(map[string]int{})
                a.NotEmpty(map[string]int{"a": 1})

                // channel(检查长度)
                ch1 := make(chan int, 10)
                a.Empty(ch1)

                ch1 <- 1
                a.NotEmpty(ch1)

                // 零值也被视为empty
                a.Empty(0)
                a.Empty(false)
            }
            ---
    c.布尔断言
        True和False检查布尔值。看似简单,但在测试复杂条件时非常有用。支持自定义错误消息,清楚说明被测试的条件。
        ---
        // 布尔断言示例
        package boolean

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

        func TestBooleanAssertions(t *testing.T) {
            a := assert.New(t)

            // ========== True / False ==========

            // 基础用法
            a.True(true)
            a.False(false)

            // 条件表达式
            a.True(5 > 3)
            a.False(5 < 3)

            // 函数返回值
            a.True(IsValid())
            a.False(IsExpired())

            // 复杂条件
            user := GetUser(1)
            a.True(user.Age >= 18, "用户应该是成年人")
            a.True(user.Active && user.Verified, "用户应该激活且已验证")

            // 字符串检查
            str := "hello world"
            a.True(strings.Contains(str, "world"))
            a.True(len(str) > 5)

            // 集合检查
            list := []int{1, 2, 3, 4, 5}
            a.True(len(list) == 5)
            a.True(list[0] == 1)

            // 自定义验证函数
            a.True(ValidateEmail("[email protected]"), "应该是有效的邮箱")
            a.False(ValidateEmail("invalid"), "应该是无效的邮箱")
        }

        type User struct {
            Age      int
            Active   bool
            Verified bool
        }

        func IsValid() bool { return true }
        func IsExpired() bool { return false }
        func GetUser(id int) User { return User{Age: 25, Active: true, Verified: true} }
        func ValidateEmail(email string) bool {
            return strings.Contains(email, "@")
        }
        ---

03.集合断言方法
    a.Contains系列
        Contains检查集合包含某个元素,支持字符串、slice、array、map。NotContains检查不包含。Subset检查子集关系。ElementsMatch检查两个集合包含相同元素(忽略顺序)。
    b.使用示例
        ---
        // 集合断言示例
        package collection

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

        func TestCollectionAssertions(t *testing.T) {
            a := assert.New(t)

            // ========== Contains / NotContains ==========

            // 字符串
            a.Contains("hello world", "world")
            a.NotContains("hello world", "goodbye")

            // 切片
            numbers := []int{1, 2, 3, 4, 5}
            a.Contains(numbers, 3)
            a.NotContains(numbers, 10)

            // map(检查键)
            m := map[string]int{"a": 1, "b": 2}
            a.Contains(m, "a")
            a.NotContains(m, "c")

            // ========== Subset / NotSubset ==========

            list := []int{1, 2, 3, 4, 5}

            // 检查子集
            a.Subset(list, []int{2, 3})
            a.Subset(list, []int{1, 5})
            a.NotSubset(list, []int{1, 10})

            // ========== ElementsMatch ==========

            // 忽略顺序的相等检查
            a.ElementsMatch(
                []int{1, 2, 3},
                []int{3, 2, 1},
            )

            // 包含重复元素也能正确处理
            a.ElementsMatch(
                []int{1, 2, 2, 3},
                []int{2, 3, 1, 2},
            )

            // 字符串切片
            a.ElementsMatch(
                []string{"apple", "banana", "cherry"},
                []string{"cherry", "apple", "banana"},
            )

            // ========== Len ==========

            // 检查长度
            a.Len([]int{1, 2, 3}, 3)
            a.Len("hello", 5)
            a.Len(map[string]int{"a": 1, "b": 2}, 2)

            ch := make(chan int, 5)
            ch <- 1
            ch <- 2
            a.Len(ch, 2)

            // ========== Unique ==========

            // 检查元素唯一性
            a.Unique([]int{1, 2, 3, 4})        // 通过
            // a.Unique([]int{1, 2, 2, 3})     // 失败:有重复

            // ========== Condition ==========

            // 自定义条件检查集合
            a.Condition(func() bool {
                list := []int{1, 2, 3, 4, 5}
                sum := 0
                for _, v := range list {
                    sum += v
                }
                return sum == 15
            }, "列表总和应该是15")
        }
        ---

04.错误断言方法
    a.Error系列
        Error检查错误不为nil,NoError检查错误为nil。EqualError检查错误消息相等。ErrorIs检查错误链包含特定错误(Go 1.13+)。ErrorAs检查错误可转换为特定类型(Go 1.13+)。ErrorContains检查错误消息包含特定字符串。
    b.使用示例
        ---
        // 错误断言示例
        package errorhandling

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

        func TestErrorAssertions(t *testing.T) {
            a := assert.New(t)

            // ========== Error / NoError ==========

            // 检查有错误
            err1 := errors.New("something went wrong")
            a.Error(err1)

            // 检查无错误
            err2 := DoSomethingSuccess()
            a.NoError(err2)

            // ========== EqualError ==========

            // 检查错误消息精确匹配
            err3 := errors.New("file not found")
            a.EqualError(err3, "file not found")

            // ========== ErrorContains ==========

            // 检查错误消息包含子字符串
            err4 := errors.New("failed to connect to database: timeout")
            a.ErrorContains(err4, "database")
            a.ErrorContains(err4, "timeout")

            // ========== ErrorIs(Go 1.13+错误链)==========

            // 定义标准错误
            var ErrNotFound = errors.New("not found")
            var ErrPermission = errors.New("permission denied")

            // 包装错误
            wrappedErr := fmt.Errorf("failed to get user: %w", ErrNotFound)

            // 检查错误链
            a.ErrorIs(wrappedErr, ErrNotFound)  // 通过
            a.NotErrorIs(wrappedErr, ErrPermission)  // 通过

            // 多层包装
            doubleWrapped := fmt.Errorf("operation failed: %w", wrappedErr)
            a.ErrorIs(doubleWrapped, ErrNotFound)  // 仍然能检测到

            // ========== ErrorAs(类型断言)==========

            // 自定义错误类型
            type ValidationError struct {
                Field   string
                Message string
            }

            func (e *ValidationError) Error() string {
                return fmt.Sprintf("%s: %s", e.Field, e.Message)
            }

            // 创建并包装自定义错误
            valErr := &ValidationError{Field: "email", Message: "invalid format"}
            wrappedValErr := fmt.Errorf("validation failed: %w", valErr)

            // 检查是否可转换为特定类型
            var target *ValidationError
            a.ErrorAs(wrappedValErr, &target)
            a.Equal("email", target.Field)
            a.Equal("invalid format", target.Message)

            // ========== 实战示例:数据库操作 ==========

            // 场景:数据库查询
            user, err := GetUserByID(999)
            if a.Error(err, "应该返回错误") {
                // 只有err不为nil时才继续检查
                a.ErrorIs(err, ErrNotFound)
                a.Nil(user)
            }

            // 场景:成功操作
            user2, err := GetUserByID(1)
            a.NoError(err, "不应该有错误")
            a.NotNil(user2, "应该返回用户对象")
        }

        func DoSomethingSuccess() error {
            return nil
        }

        var ErrNotFound = errors.New("not found")

        type User struct {
            ID   int
            Name string
        }

        func GetUserByID(id int) (*User, error) {
            if id == 999 {
                return nil, fmt.Errorf("user query failed: %w", ErrNotFound)
            }
            return &User{ID: id, Name: "Alice"}, nil
        }
        ---

2.3 require包

01.包设计理念
    a.致命性断言
        require包的核心特性是致命性断言,断言失败时立即终止当前测试函数的执行。这通过调用testing.T的FailNow方法实现,该方法会立即终止当前goroutine。require包适用于前置条件检查,当前置条件不满足时,继续执行测试没有意义且可能导致panic或产生误导性错误。
    b.与assert包的关系
        require包的API与assert包完全一致,所有assert包的方法在require包都有对应版本。实现上,require包在assert包基础上添加了FailNow调用。开发者可以根据语义选择使用assert或require,而无需学习不同的API。
    c.使用场景
        require最适合检查测试的前置条件,如资源初始化、数据准备、依赖创建等。如果这些前置条件不满足,后续测试代码会在错误状态下执行,产生大量无意义的错误信息甚至panic。使用require可以快速失败快速反馈,提高测试的诊断效率。
    d.设计示例
        ---
        // require包设计理念示例
        package requiredesign

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

        // ========== 场景1:资源初始化 ==========

        // 错误示例:使用assert(可能导致panic)
        func TestBadResourceInit(t *testing.T) {
            a := assert.New(t)

            // 数据库连接失败,但测试继续
            db, err := sql.Open("mysql", "invalid-connection")
            a.NoError(err)  // ❌ 失败但继续执行

            // db可能为nil,这里会panic!
            rows, _ := db.Query("SELECT * FROM users")
            rows.Close()
        }

        // 正确示例:使用require(立即终止)
        func TestGoodResourceInit(t *testing.T) {
            r := require.New(t)

            // 数据库连接失败,立即终止测试
            db, err := sql.Open("mysql", "valid-connection")
            r.NoError(err, "数据库连接必须成功")  // ✅ 失败立即终止
            defer db.Close()

            // 确保db不为nil后才执行
            rows, err := db.Query("SELECT * FROM users")
            r.NoError(err)
            defer rows.Close()
        }

        // ========== 场景2:对象存在性检查 ==========

        // 错误示例:使用assert(可能访问nil)
        func TestBadNilCheck(t *testing.T) {
            a := assert.New(t)

            config := LoadConfig()
            a.NotNil(config)  // ❌ 失败但继续

            // config可能为nil,访问字段会panic
            a.Equal("localhost", config.Host)  // panic!
        }

        // 正确示例:使用require(安全终止)
        func TestGoodNilCheck(t *testing.T) {
            r := require.New(t)
            a := assert.New(t)

            config := LoadConfig()
            r.NotNil(config, "配置必须加载成功")  // ✅ 失败立即终止

            // 确保config不为nil后才访问字段
            a.Equal("localhost", config.Host)
            a.Equal(3306, config.Port)
        }

        // ========== 场景3:多层依赖初始化 ==========

        func TestMultiLayerInit(t *testing.T) {
            r := require.New(t)
            a := assert.New(t)

            // 第一层:配置
            config := LoadConfig()
            r.NotNil(config, "配置必须存在")

            // 第二层:数据库
            db, err := ConnectDB(config.DBHost)
            r.NoError(err, "数据库连接必须成功")
            r.NotNil(db)
            defer db.Close()

            // 第三层:仓库
            repo := NewUserRepository(db)
            r.NotNil(repo, "仓库初始化必须成功")

            // 第四层:服务
            service := NewUserService(repo)
            r.NotNil(service, "服务初始化必须成功")

            // 现在可以安全地测试业务逻辑(使用assert)
            users, err := service.GetAllUsers()
            a.NoError(err)
            a.NotEmpty(users)
        }

        // ========== 场景4:测试数据准备 ==========

        func TestDataPreparation(t *testing.T) {
            r := require.New(t)
            a := assert.New(t)

            // 准备测试数据(必须成功)
            db := SetupTestDB(t)
            r.NotNil(db)
            defer CleanupTestDB(t, db)

            // 插入测试数据(必须成功)
            err := InsertTestUsers(db, []User{
                {Name: "Alice", Age: 25},
                {Name: "Bob", Age: 30},
            })
            r.NoError(err, "测试数据插入必须成功")

            // 现在测试查询功能(可以用assert)
            users, err := QueryUsers(db)
            a.NoError(err)
            a.Len(users, 2)
            a.Equal("Alice", users[0].Name)
        }

        // ========== 场景5:文件操作 ==========

        func TestFileOperations(t *testing.T) {
            r := require.New(t)
            a := assert.New(t)

            // 创建临时目录(必须成功)
            tmpDir, err := os.MkdirTemp("", "test-*")
            r.NoError(err, "临时目录创建必须成功")
            defer os.RemoveAll(tmpDir)

            // 创建测试文件(必须成功)
            testFile := filepath.Join(tmpDir, "test.txt")
            err = os.WriteFile(testFile, []byte("test content"), 0644)
            r.NoError(err, "文件写入必须成功")

            // 测试读取功能(可以用assert)
            content, err := os.ReadFile(testFile)
            a.NoError(err)
            a.Equal("test content", string(content))
        }

        // 辅助类型定义
        type Config struct {
            Host   string
            Port   int
            DBHost string
        }

        type User struct {
            Name string
            Age  int
        }

        func LoadConfig() *Config {
            return &Config{Host: "localhost", Port: 8080, DBHost: "localhost:3306"}
        }

        func ConnectDB(host string) (*sql.DB, error) {
            return nil, nil
        }

        func NewUserRepository(db *sql.DB) interface{} {
            return &struct{}{}
        }

        func NewUserService(repo interface{}) interface{} {
            return &struct{}{}
        }

        func SetupTestDB(t *testing.T) *sql.DB {
            return nil
        }

        func CleanupTestDB(t *testing.T, db *sql.DB) {}

        func InsertTestUsers(db *sql.DB, users []User) error {
            return nil
        }

        func QueryUsers(db *sql.DB) ([]User, error) {
            return []User{{Name: "Alice", Age: 25}, {Name: "Bob", Age: 30}}, nil
        }
        ---

02.核心方法
    a.API完全一致
        require包提供的所有方法与assert包一一对应,包括Equal、NotEqual、Nil、NotNil、True、False、NoError、Error、Contains、Len等所有断言方法。唯一的区别是行为:assert继续执行,require立即终止。
    b.方法对照
        ---
        // require包核心方法示例
        package requiremethods

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

        func TestRequireMethods(t *testing.T) {
            r := require.New(t)

            // ========== 相等性方法 ==========

            r.Equal(5, Add(2, 3))
            r.NotEqual(5, Add(2, 2))
            r.EqualValues(int32(5), int64(5))

            // ========== 存在性方法 ==========

            config := LoadConfig()
            r.NotNil(config)
            r.NotZero(config.Port)
            r.NotEmpty(config.Host)

            // ========== 布尔方法 ==========

            r.True(config.Port > 0)
            r.False(config.Host == "")

            // ========== 集合方法 ==========

            users := GetUsers()
            r.Len(users, 3)
            r.Contains(users, "Alice")
            r.NotContains(users, "Unknown")

            // ========== 错误方法 ==========

            err := ValidateConfig(config)
            r.NoError(err)

            err2 := ValidateConfig(nil)
            r.Error(err2)
            r.ErrorContains(err2, "config is nil")

            // ========== 类型方法 ==========

            var obj interface{} = "hello"
            r.IsType("", obj)
            r.Implements((*fmt.Stringer)(nil), obj)

            // ========== panic方法 ==========

            r.Panics(func() {
                panic("test panic")
            })

            r.NotPanics(func() {
                _ = Add(2, 3)
            })
        }

        // ========== require vs assert 方法对比 ==========

        func TestRequireVsAssert(t *testing.T) {
            r := require.New(t)
            a := assert.New(t)

            // require:前置条件检查
            db := ConnectDatabase()
            r.NotNil(db)  // 失败则终止
            defer db.Close()

            // assert:多个独立检查
            users, err := db.QueryUsers()
            a.NoError(err)         // 失败继续
            a.NotEmpty(users)      // 失败继续
            a.Len(users, 5)        // 失败继续
            a.Equal("Alice", users[0].Name)  // 失败继续
            // 可以看到所有失败的断言
        }

        // 辅助函数
        func Add(a, b int) int { return a + b }

        type Config struct {
            Host string
            Port int
        }

        func LoadConfig() *Config {
            return &Config{Host: "localhost", Port: 8080}
        }

        func GetUsers() []string {
            return []string{"Alice", "Bob", "Charlie"}
        }

        func ValidateConfig(config *Config) error {
            if config == nil {
                return errors.New("config is nil")
            }
            return nil
        }

        type Database struct{}

        func ConnectDatabase() *Database {
            return &Database{}
        }

        func (db *Database) Close() {}

        func (db *Database) QueryUsers() ([]User, error) {
            return []User{{Name: "Alice"}}, nil
        }
        ---

03.实战模式
    a.前置条件检查模式
        在测试开始时使用require检查所有前置条件,确保测试环境正确初始化。包括资源创建、配置加载、依赖注入、测试数据准备等。这些步骤失败意味着测试无法正常进行,应该立即终止。
    b.资源初始化模式
        使用require确保资源初始化成功,配合defer进行资源清理。这是Go语言资源管理的标准模式,require确保资源可用,defer确保资源释放。
    c.实战示例
        ---
        // require包实战模式
        package requirepatterns

        import (
            "testing"
            "github.com/stretchr/testify/require"
            "github.com/stretchr/testify/assert"
            "database/sql"
            "context"
            "time"
        )

        // ========== 模式1:完整的集成测试 ==========

        func TestIntegrationPattern(t *testing.T) {
            r := require.New(t)
            a := assert.New(t)

            // 阶段1:环境准备(require)
            config := LoadTestConfig()
            r.NotNil(config, "测试配置必须加载")

            // 阶段2:数据库连接(require)
            db, err := sql.Open("postgres", config.DatabaseURL)
            r.NoError(err, "数据库连接必须成功")
            r.NotNil(db)
            defer db.Close()

            // 验证连接
            ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
            defer cancel()
            err = db.PingContext(ctx)
            r.NoError(err, "数据库ping必须成功")

            // 阶段3:事务准备(require)
            tx, err := db.BeginTx(ctx, nil)
            r.NoError(err, "事务开始必须成功")
            defer tx.Rollback()

            // 阶段4:测试数据准备(require)
            _, err = tx.ExecContext(ctx, "INSERT INTO users (name, email) VALUES ($1, $2)",
                "Alice", "[email protected]")
            r.NoError(err, "测试数据插入必须成功")

            // 阶段5:执行测试(assert)
            var name, email string
            err = tx.QueryRowContext(ctx, "SELECT name, email FROM users WHERE name = $1", "Alice").
                Scan(&name, &email)
            a.NoError(err)
            a.Equal("Alice", name)
            a.Equal("[email protected]", email)
        }

        // ========== 模式2:HTTP服务测试 ==========

        func TestHTTPServicePattern(t *testing.T) {
            r := require.New(t)
            a := assert.New(t)

            // 阶段1:启动测试服务器(require)
            server := NewTestServer()
            r.NotNil(server)
            defer server.Close()

            err := server.Start()
            r.NoError(err, "服务器启动必须成功")

            // 等待服务器就绪
            r.Eventually(func() bool {
                return server.IsReady()
            }, 5*time.Second, 100*time.Millisecond, "服务器必须在5秒内就绪")

            // 阶段2:创建HTTP客户端(require)
            client := &http.Client{Timeout: 5 * time.Second}
            r.NotNil(client)

            // 阶段3:执行测试请求(assert)
            resp, err := client.Get(server.URL + "/health")
            a.NoError(err)
            a.Equal(http.StatusOK, resp.StatusCode)
            defer resp.Body.Close()

            body, err := io.ReadAll(resp.Body)
            a.NoError(err)
            a.Contains(string(body), "healthy")
        }

        // ========== 模式3:文件系统测试 ==========

        func TestFileSystemPattern(t *testing.T) {
            r := require.New(t)
            a := assert.New(t)

            // 阶段1:创建临时目录(require)
            tmpDir, err := os.MkdirTemp("", "test-*")
            r.NoError(err, "临时目录创建必须成功")
            defer os.RemoveAll(tmpDir)

            // 阶段2:创建子目录结构(require)
            dataDir := filepath.Join(tmpDir, "data")
            err = os.MkdirAll(dataDir, 0755)
            r.NoError(err, "子目录创建必须成功")

            // 阶段3:创建测试文件(require)
            testFile := filepath.Join(dataDir, "test.json")
            testData := []byte(`{"name":"Alice","age":25}`)
            err = os.WriteFile(testFile, testData, 0644)
            r.NoError(err, "测试文件创建必须成功")

            // 验证文件存在
            _, err = os.Stat(testFile)
            r.NoError(err, "文件必须存在")

            // 阶段4:测试文件操作(assert)
            content, err := os.ReadFile(testFile)
            a.NoError(err)
            a.JSONEq(string(testData), string(content))

            // 测试修改文件
            newData := []byte(`{"name":"Bob","age":30}`)
            err = os.WriteFile(testFile, newData, 0644)
            a.NoError(err)

            content, err = os.ReadFile(testFile)
            a.NoError(err)
            a.JSONEq(string(newData), string(content))
        }

        // ========== 模式4:并发测试 ==========

        func TestConcurrencyPattern(t *testing.T) {
            r := require.New(t)
            a := assert.New(t)

            // 阶段1:创建并发安全的数据结构(require)
            cache := NewSafeCache()
            r.NotNil(cache)

            // 阶段2:并发写入
            const goroutines = 100
            const itemsPerGoroutine = 10

            done := make(chan bool, goroutines)
            for i := 0; i < goroutines; i++ {
                go func(id int) {
                    for j := 0; j < itemsPerGoroutine; j++ {
                        key := fmt.Sprintf("key-%d-%d", id, j)
                        cache.Set(key, id*itemsPerGoroutine+j)
                    }
                    done <- true
                }(i)
            }

            // 等待所有goroutine完成
            for i := 0; i < goroutines; i++ {
                select {
                case <-done:
                case <-time.After(5 * time.Second):
                    r.Fail("并发写入超时")
                }
            }

            // 阶段3:验证结果(assert)
            expectedSize := goroutines * itemsPerGoroutine
            a.Equal(expectedSize, cache.Size())

            // 验证数据完整性
            for i := 0; i < goroutines; i++ {
                for j := 0; j < itemsPerGoroutine; j++ {
                    key := fmt.Sprintf("key-%d-%d", i, j)
                    value, exists := cache.Get(key)
                    a.True(exists, "键应该存在: %s", key)
                    a.Equal(i*itemsPerGoroutine+j, value)
                }
            }
        }

        // ========== 模式5:错误场景测试 ==========

        func TestErrorScenariosPattern(t *testing.T) {
            r := require.New(t)
            a := assert.New(t)

            // 场景1:测试正常流程(作为基准)
            t.Run("正常流程", func(t *testing.T) {
                r := require.New(t)
                a := assert.New(t)

                service := NewUserService()
                r.NotNil(service)

                user, err := service.CreateUser("Alice", "[email protected]")
                a.NoError(err)
                a.NotNil(user)
                a.Equal("Alice", user.Name)
            })

            // 场景2:测试参数验证错误
            t.Run("空名称错误", func(t *testing.T) {
                r := require.New(t)
                a := assert.New(t)

                service := NewUserService()
                r.NotNil(service)

                user, err := service.CreateUser("", "[email protected]")
                a.Error(err)
                a.Nil(user)
                a.ErrorContains(err, "name is required")
            })

            // 场景3:测试资源不足错误
            t.Run("数据库连接失败", func(t *testing.T) {
                r := require.New(t)
                a := assert.New(t)

                // 模拟数据库不可用
                service := NewUserServiceWithDB(nil)
                r.NotNil(service)

                user, err := service.CreateUser("Alice", "[email protected]")
                a.Error(err)
                a.Nil(user)
                a.ErrorContains(err, "database")
            })
        }

        // 辅助类型定义
        type TestConfig struct {
            DatabaseURL string
        }

        type TestServer struct {
            URL string
        }

        type SafeCache struct {
            data map[string]int
            mu   sync.RWMutex
        }

        type UserService struct {
            db interface{}
        }

        func LoadTestConfig() *TestConfig {
            return &TestConfig{DatabaseURL: "postgres://localhost/test"}
        }

        func NewTestServer() *TestServer {
            return &TestServer{URL: "http://localhost:8080"}
        }

        func (s *TestServer) Start() error {
            return nil
        }

        func (s *TestServer) IsReady() bool {
            return true
        }

        func (s *TestServer) Close() {}

        func NewSafeCache() *SafeCache {
            return &SafeCache{data: make(map[string]int)}
        }

        func (c *SafeCache) Set(key string, value int) {
            c.mu.Lock()
            defer c.mu.Unlock()
            c.data[key] = value
        }

        func (c *SafeCache) Get(key string) (int, bool) {
            c.mu.RLock()
            defer c.mu.RUnlock()
            val, ok := c.data[key]
            return val, ok
        }

        func (c *SafeCache) Size() int {
            c.mu.RLock()
            defer c.mu.RUnlock()
            return len(c.data)
        }

        func NewUserService() *UserService {
            return &UserService{}
        }

        func NewUserServiceWithDB(db interface{}) *UserService {
            return &UserService{db: db}
        }

        func (s *UserService) CreateUser(name, email string) (*User, error) {
            if name == "" {
                return nil, errors.New("name is required")
            }
            if s.db == nil {
                return nil, errors.New("database not available")
            }
            return &User{Name: name}, nil
        }
        ---

04.最佳实践
    a.require优先原则
        在测试开始时,优先使用require检查所有前置条件。确保测试环境正确后,再用assert进行业务逻辑验证。这种模式使测试更加健壮,错误信息更加清晰。
    b.资源管理原则
        所有通过require初始化的资源都应该配合defer进行清理。即使测试失败,defer也会执行,确保资源不泄漏。这是Go语言的最佳实践,require强化了这个模式。
    c.错误信息原则
        require断言失败会立即终止测试,因此错误消息特别重要。应该提供清晰的错误消息,说明失败的原因和影响。帮助开发者快速定位问题根源。

2.4 mock包

01.Mock框架概述
    a.设计目标
        testify的mock包提供完整的模拟对象框架,用于在测试中隔离外部依赖。通过mock,可以控制依赖的行为、验证方法调用、模拟各种场景包括成功、失败、超时等。Mock使单元测试真正成为单元测试,而不是集成测试。
    b.核心机制
        mock包的核心是Mock结构体,包含期望列表ExpectedCalls和实际调用列表Calls。通过On方法设置期望,Called方法记录调用,AssertExpectations方法验证期望。使用mutex保证线程安全,支持并发测试场景。
    c.使用流程
        Mock测试的标准流程包括四个步骤:定义接口、创建Mock对象、设置期望、执行测试、验证调用。这种结构化流程确保测试的完整性和可维护性。
    d.基础示例
        ---
        // mock包基础示例
        package mockbasic

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

        // ========== 步骤1:定义接口 ==========

        // 数据仓库接口
        type UserRepository interface {
            GetByID(id int) (*User, error)
            Save(user *User) error
            Delete(id int) error
            FindAll() ([]*User, error)
        }

        // 业务服务
        type UserService struct {
            repo UserRepository
        }

        func (s *UserService) GetUser(id int) (*User, error) {
            return s.repo.GetByID(id)
        }

        func (s *UserService) CreateUser(name, email string) error {
            user := &User{Name: name, Email: email}
            return s.repo.Save(user)
        }

        // ========== 步骤2:创建Mock对象 ==========

        // Mock对象(嵌入mock.Mock)
        type MockUserRepository struct {
            mock.Mock
        }

        // 实现接口方法
        func (m *MockUserRepository) GetByID(id int) (*User, error) {
            // Called记录调用并返回预设值
            args := m.Called(id)

            // 处理nil返回值
            if args.Get(0) == nil {
                return nil, args.Error(1)
            }
            return args.Get(0).(*User), args.Error(1)
        }

        func (m *MockUserRepository) Save(user *User) error {
            args := m.Called(user)
            return args.Error(0)
        }

        func (m *MockUserRepository) Delete(id int) error {
            args := m.Called(id)
            return args.Error(0)
        }

        func (m *MockUserRepository) FindAll() ([]*User, error) {
            args := m.Called()
            if args.Get(0) == nil {
                return nil, args.Error(1)
            }
            return args.Get(0).([]*User), args.Error(1)
        }

        // ========== 步骤3:编写测试 ==========

        // 测试1:基础Mock使用
        func TestBasicMock(t *testing.T) {
            // 创建mock对象
            mockRepo := new(MockUserRepository)

            // 设置期望:当调用GetByID(1)时返回指定用户
            expectedUser := &User{ID: 1, Name: "Alice", Email: "[email protected]"}
            mockRepo.On("GetByID", 1).Return(expectedUser, nil)

            // 创建服务并注入mock
            service := &UserService{repo: mockRepo}

            // 执行测试
            user, err := service.GetUser(1)

            // 断言结果
            assert.NoError(t, err)
            assert.Equal(t, expectedUser, user)

            // 验证mock期望
            mockRepo.AssertExpectations(t)
        }

        // 测试2:模拟错误场景
        func TestMockError(t *testing.T) {
            mockRepo := new(MockUserRepository)

            // 设置期望:返回错误
            mockRepo.On("GetByID", 999).Return(nil, errors.New("user not found"))

            service := &UserService{repo: mockRepo}
            user, err := service.GetUser(999)

            // 验证错误处理
            assert.Error(t, err)
            assert.Nil(t, user)
            assert.Contains(t, err.Error(), "not found")

            mockRepo.AssertExpectations(t)
        }

        // 测试3:多次调用
        func TestMultipleCalls(t *testing.T) {
            mockRepo := new(MockUserRepository)

            // 为不同参数设置不同期望
            mockRepo.On("GetByID", 1).Return(&User{ID: 1, Name: "Alice"}, nil)
            mockRepo.On("GetByID", 2).Return(&User{ID: 2, Name: "Bob"}, nil)
            mockRepo.On("GetByID", 3).Return(nil, errors.New("not found"))

            service := &UserService{repo: mockRepo}

            // 测试第一次调用
            user1, err1 := service.GetUser(1)
            assert.NoError(t, err1)
            assert.Equal(t, "Alice", user1.Name)

            // 测试第二次调用
            user2, err2 := service.GetUser(2)
            assert.NoError(t, err2)
            assert.Equal(t, "Bob", user2.Name)

            // 测试第三次调用(错误场景)
            user3, err3 := service.GetUser(3)
            assert.Error(t, err3)
            assert.Nil(t, user3)

            mockRepo.AssertExpectations(t)
        }

        // 测试4:验证方法调用
        func TestVerifyCall(t *testing.T) {
            mockRepo := new(MockUserRepository)

            newUser := &User{Name: "Charlie", Email: "[email protected]"}
            mockRepo.On("Save", newUser).Return(nil)

            service := &UserService{repo: mockRepo}
            err := service.CreateUser("Charlie", "[email protected]")

            assert.NoError(t, err)

            // 验证Save方法被调用了一次,参数正确
            mockRepo.AssertCalled(t, "Save", newUser)
            mockRepo.AssertNumberOfCalls(t, "Save", 1)
        }

        // 数据类型定义
        type User struct {
            ID    int
            Name  string
            Email string
        }
        ---

02.参数匹配器
    a.匹配器类型
        testify提供多种参数匹配器。Anything匹配任意值,AnythingOfType匹配特定类型,MatchedBy使用自定义函数匹配。这些匹配器增强了mock的灵活性,可以处理复杂的参数验证场景。
    b.匹配器示例
        ---
        // 参数匹配器详解
        package matchers

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

        // ========== Anything匹配器 ==========

        func TestAnythingMatcher(t *testing.T) {
            mockRepo := new(MockUserRepository)

            // Anything匹配任意参数
            mockRepo.On("GetByID", mock.Anything).Return(&User{Name: "Default"}, nil)

            // 任何ID都会匹配
            user1, _ := mockRepo.GetByID(1)
            assert.Equal(t, "Default", user1.Name)

            user2, _ := mockRepo.GetByID(999)
            assert.Equal(t, "Default", user2.Name)

            mockRepo.AssertExpectations(t)
        }

        // ========== AnythingOfType匹配器 ==========

        func TestAnythingOfTypeMatcher(t *testing.T) {
            mockRepo := new(MockUserRepository)

            // AnythingOfType匹配特定类型
            mockRepo.On("Save", mock.AnythingOfType("*mockbasic.User")).Return(nil)

            // 任何User指针都会匹配
            err1 := mockRepo.Save(&User{Name: "Alice"})
            assert.NoError(t, err1)

            err2 := mockRepo.Save(&User{Name: "Bob"})
            assert.NoError(t, err2)

            mockRepo.AssertExpectations(t)
        }

        // ========== MatchedBy自定义匹配器 ==========

        func TestMatchedByMatcher(t *testing.T) {
            mockRepo := new(MockUserRepository)

            // 自定义匹配函数:只匹配正数ID
            mockRepo.On("GetByID", mock.MatchedBy(func(id int) bool {
                return id > 0 && id < 1000
            })).Return(&User{Name: "Valid ID"}, nil)

            // 匹配成功
            user1, _ := mockRepo.GetByID(1)
            assert.Equal(t, "Valid ID", user1.Name)

            user2, _ := mockRepo.GetByID(500)
            assert.Equal(t, "Valid ID", user2.Name)

            // 不匹配的调用会panic
            // mockRepo.GetByID(1000)  // panic

            mockRepo.AssertExpectations(t)
        }

        // ========== 复杂匹配器示例 ==========

        func TestComplexMatchers(t *testing.T) {
            mockRepo := new(MockUserRepository)

            // 匹配特定条件的User对象
            mockRepo.On("Save", mock.MatchedBy(func(user *User) bool {
                // 只接受年龄在18-65之间的用户
                return user.Age >= 18 && user.Age <= 65 && user.Email != ""
            })).Return(nil)

            // 匹配成功
            err1 := mockRepo.Save(&User{Name: "Alice", Age: 25, Email: "[email protected]"})
            assert.NoError(t, err1)

            // 不匹配(年龄太小)
            // mockRepo.Save(&User{Name: "Bob", Age: 16, Email: "[email protected]"})  // panic

            mockRepo.AssertExpectations(t)
        }

        // ========== 混合使用匹配器 ==========

        func TestMixedMatchers(t *testing.T) {
            mockService := new(MockService)

            // 第一个参数精确匹配,第二个参数任意
            mockService.On("Process", 1, mock.Anything).Return("result1", nil)

            // 两个参数都用匹配器
            mockService.On("Process",
                mock.MatchedBy(func(id int) bool { return id > 100 }),
                mock.AnythingOfType("string"),
            ).Return("result2", nil)

            result1, _ := mockService.Process(1, "any value")
            assert.Equal(t, "result1", result1)

            result2, _ := mockService.Process(200, "test")
            assert.Equal(t, "result2", result2)

            mockService.AssertExpectations(t)
        }

        // Mock Service定义
        type MockService struct {
            mock.Mock
        }

        func (m *MockService) Process(id int, data string) (string, error) {
            args := m.Called(id, data)
            return args.String(0), args.Error(1)
        }

        type User struct {
            Name  string
            Age   int
            Email string
        }

        type MockUserRepository struct {
            mock.Mock
        }

        func (m *MockUserRepository) GetByID(id int) (*User, error) {
            args := m.Called(id)
            if args.Get(0) == nil {
                return nil, args.Error(1)
            }
            return args.Get(0).(*User), args.Error(1)
        }

        func (m *MockUserRepository) Save(user *User) error {
            args := m.Called(user)
            return args.Error(0)
        }
        ---

03.调用控制
    a.调用次数控制
        testify提供多种调用次数控制方法。Once表示调用一次,Twice表示调用两次,Times(n)表示调用n次。Maybe表示可选调用。通过调用次数控制,可以精确验证方法被调用的情况。
    b.调用行为控制
        Run方法可以在调用时执行自定义函数,After方法可以延迟返回模拟超时,WaitUntil方法可以等待特定时间。这些方法让mock更加灵活,可以模拟各种复杂场景。
    c.控制示例
        ---
        // 调用控制示例
        package callcontrol

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

        // ========== 调用次数控制 ==========

        func TestCallTimes(t *testing.T) {
            mockRepo := new(MockUserRepository)

            // Once:期望调用一次
            mockRepo.On("GetByID", 1).Return(&User{Name: "Alice"}, nil).Once()

            // Twice:期望调用两次
            mockRepo.On("Save", mock.Anything).Return(nil).Twice()

            // Times(n):期望调用n次
            mockRepo.On("Delete", mock.Anything).Return(nil).Times(3)

            // 执行调用
            mockRepo.GetByID(1)

            mockRepo.Save(&User{Name: "Bob"})
            mockRepo.Save(&User{Name: "Charlie"})

            mockRepo.Delete(1)
            mockRepo.Delete(2)
            mockRepo.Delete(3)

            // 验证调用次数
            mockRepo.AssertExpectations(t)
            mockRepo.AssertNumberOfCalls(t, "GetByID", 1)
            mockRepo.AssertNumberOfCalls(t, "Save", 2)
            mockRepo.AssertNumberOfCalls(t, "Delete", 3)
        }

        // ========== Maybe:可选调用 ==========

        func TestMaybeCall(t *testing.T) {
            mockRepo := new(MockUserRepository)

            // Maybe:调用或不调用都可以
            mockRepo.On("GetByID", 1).Return(&User{Name: "Alice"}, nil).Maybe()

            // 不调用也不会失败
            mockRepo.AssertExpectations(t)

            // 调用了也不会失败
            mockRepo.GetByID(1)
            mockRepo.AssertExpectations(t)
        }

        // ========== Run:自定义执行函数 ==========

        func TestRunFunction(t *testing.T) {
            mockRepo := new(MockUserRepository)

            callCount := 0

            // Run:每次调用时执行自定义函数
            mockRepo.On("GetByID", mock.Anything).Run(func(args mock.Arguments) {
                callCount++
                id := args.Int(0)
                t.Logf("GetByID called with id=%d, total calls=%d", id, callCount)
            }).Return(&User{Name: "Dynamic"}, nil)

            // 调用3次
            mockRepo.GetByID(1)
            mockRepo.GetByID(2)
            mockRepo.GetByID(3)

            assert.Equal(t, 3, callCount)
            mockRepo.AssertExpectations(t)
        }

        // ========== After:延迟返回 ==========

        func TestAfterDelay(t *testing.T) {
            mockRepo := new(MockUserRepository)

            // After:延迟100ms后返回(模拟慢查询)
            mockRepo.On("GetByID", 1).
                After(100 * time.Millisecond).
                Return(&User{Name: "Slow"}, nil)

            start := time.Now()
            user, err := mockRepo.GetByID(1)
            duration := time.Since(start)

            assert.NoError(t, err)
            assert.Equal(t, "Slow", user.Name)
            assert.GreaterOrEqual(t, duration, 100*time.Millisecond)

            mockRepo.AssertExpectations(t)
        }

        // ========== WaitUntil:等待通道 ==========

        func TestWaitUntil(t *testing.T) {
            mockRepo := new(MockUserRepository)

            // 创建通道
            ready := make(chan bool)

            // WaitUntil:等待通道信号
            mockRepo.On("GetByID", 1).
                WaitUntil(ready).
                Return(&User{Name: "Ready"}, nil)

            // 在goroutine中延迟发送信号
            go func() {
                time.Sleep(50 * time.Millisecond)
                close(ready)
            }()

            start := time.Now()
            user, err := mockRepo.GetByID(1)
            duration := time.Since(start)

            assert.NoError(t, err)
            assert.Equal(t, "Ready", user.Name)
            assert.GreaterOrEqual(t, duration, 50*time.Millisecond)

            mockRepo.AssertExpectations(t)
        }

        // ========== Return函数:动态返回值 ==========

        func TestReturnFunction(t *testing.T) {
            mockRepo := new(MockUserRepository)

            // Return函数:根据参数动态生成返回值
            mockRepo.On("GetByID", mock.Anything).Return(
                func(id int) *User {
                    return &User{
                        ID:   id,
                        Name: fmt.Sprintf("User-%d", id),
                    }
                },
                nil,
            )

            // 不同参数得到不同返回值
            user1, _ := mockRepo.GetByID(1)
            assert.Equal(t, 1, user1.ID)
            assert.Equal(t, "User-1", user1.Name)

            user2, _ := mockRepo.GetByID(100)
            assert.Equal(t, 100, user2.ID)
            assert.Equal(t, "User-100", user2.Name)

            mockRepo.AssertExpectations(t)
        }

        // ========== 组合使用 ==========

        func TestCombinedControl(t *testing.T) {
            mockRepo := new(MockUserRepository)

            callLog := []int{}

            // 组合使用多个控制方法
            mockRepo.On("GetByID", mock.Anything).
                Run(func(args mock.Arguments) {
                    id := args.Int(0)
                    callLog = append(callLog, id)
                }).
                After(10 * time.Millisecond).
                Return(func(id int) *User {
                    return &User{ID: id, Name: "User"}
                }, nil).
                Times(3)

            // 调用3次
            for i := 1; i <= 3; i++ {
                start := time.Now()
                user, _ := mockRepo.GetByID(i)
                duration := time.Since(start)

                assert.Equal(t, i, user.ID)
                assert.GreaterOrEqual(t, duration, 10*time.Millisecond)
            }

            assert.Equal(t, []int{1, 2, 3}, callLog)
            mockRepo.AssertExpectations(t)
        }

        type User struct {
            ID   int
            Name string
        }

        type MockUserRepository struct {
            mock.Mock
        }

        func (m *MockUserRepository) GetByID(id int) (*User, error) {
            args := m.Called(id)
            if fn, ok := args.Get(0).(func(int) *User); ok {
                return fn(id), args.Error(1)
            }
            if args.Get(0) == nil {
                return nil, args.Error(1)
            }
            return args.Get(0).(*User), args.Error(1)
        }

        func (m *MockUserRepository) Save(user *User) error {
            args := m.Called(user)
            return args.Error(0)
        }

        func (m *MockUserRepository) Delete(id int) error {
            args := m.Called(id)
            return args.Error(0)
        }
        ---

04.验证方法
    a.期望验证
        AssertExpectations验证所有期望都被满足,AssertCalled验证特定方法被调用,AssertNotCalled验证方法未被调用。AssertNumberOfCalls验证调用次数。这些方法确保mock对象按预期被使用。
    b.验证示例
        ---
        // Mock验证方法详解
        package verification

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

        func TestMockVerification(t *testing.T) {
            mockRepo := new(MockUserRepository)

            // 设置期望
            mockRepo.On("GetByID", 1).Return(&User{Name: "Alice"}, nil)
            mockRepo.On("Save", mock.Anything).Return(nil)

            // 执行调用
            mockRepo.GetByID(1)
            mockRepo.Save(&User{Name: "Bob"})

            // AssertExpectations:验证所有期望
            mockRepo.AssertExpectations(t)

            // AssertCalled:验证特定方法被调用
            mockRepo.AssertCalled(t, "GetByID", 1)
            mockRepo.AssertCalled(t, "Save", mock.Anything)

            // AssertNumberOfCalls:验证调用次数
            mockRepo.AssertNumberOfCalls(t, "GetByID", 1)
            mockRepo.AssertNumberOfCalls(t, "Save", 1)

            // AssertNotCalled:验证方法未被调用
            mockRepo.AssertNotCalled(t, "Delete", mock.Anything)
        }

        type User struct{ Name string }
        type MockUserRepository struct{ mock.Mock }
        func (m *MockUserRepository) GetByID(id int) (*User, error) {
            args := m.Called(id)
            if args.Get(0) == nil {
                return nil, args.Error(1)
            }
            return args.Get(0).(*User), args.Error(1)
        }
        func (m *MockUserRepository) Save(user *User) error {
            args := m.Called(user)
            return args.Error(0)
        }
        func (m *MockUserRepository) Delete(id int) error {
            args := m.Called(id)
            return args.Error(0)
        }
        ---

2.5 suite包

01.测试套件概念
    a.设计目的
        suite包提供测试套件功能,允许将相关测试组织成结构体,共享初始化逻辑和测试夹具。通过生命周期钩子函数SetUp和TearDown,可以在测试前后执行准备和清理工作。suite使测试代码更加结构化和可维护。
    b.核心组件
        Suite接口定义测试套件的基本结构,TestingSuite结构体提供T()方法访问testing.T。生命周期钩子包括SetupSuite、TearDownSuite、SetupTest、TearDownTest等。Run函数通过反射扫描并执行所有以Test开头的方法。
    c.适用场景
        suite适合需要复杂初始化的场景,如数据库连接、文件系统准备、外部服务启动等。适合有多个相关测试需要共享状态的场景。适合集成测试和端到端测试,不太适合简单的单元测试。
    d.基础示例
        ---
        // suite包基础示例
        package suitebasic

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

        // ========== 步骤1:定义测试套件结构体 ==========

        type UserRepositoryTestSuite struct {
            suite.Suite              // 嵌入suite.Suite
            db   *sql.DB            // 共享资源
            repo *UserRepository    // 被测对象
        }

        // ========== 步骤2:实现生命周期钩子 ==========

        // SetupSuite:整个套件开始前执行一次
        func (s *UserRepositoryTestSuite) SetupSuite() {
            // 连接测试数据库
            var err error
            s.db, err = sql.Open("mysql", "test:test@tcp(localhost:3306)/testdb")
            s.Require().NoError(err, "数据库连接必须成功")

            // 创建测试表
            _, err = s.db.Exec(`
                CREATE TABLE IF NOT EXISTS users (
                    id INT PRIMARY KEY AUTO_INCREMENT,
                    name VARCHAR(100),
                    email VARCHAR(100)
                )
            `)
            s.Require().NoError(err)
        }

        // TearDownSuite:整个套件结束后执行一次
        func (s *UserRepositoryTestSuite) TearDownSuite() {
            // 清理测试表
            s.db.Exec("DROP TABLE IF EXISTS users")

            // 关闭数据库连接
            if s.db != nil {
                s.db.Close()
            }
        }

        // SetupTest:每个测试方法前执行
        func (s *UserRepositoryTestSuite) SetupTest() {
            // 清空表数据
            s.db.Exec("TRUNCATE TABLE users")

            // 插入测试数据
            s.db.Exec("INSERT INTO users (name, email) VALUES ('Alice', '[email protected]')")
            s.db.Exec("INSERT INTO users (name, email) VALUES ('Bob', '[email protected]')")

            // 创建被测对象
            s.repo = NewUserRepository(s.db)
        }

        // TearDownTest:每个测试方法后执行
        func (s *UserRepositoryTestSuite) TearDownTest() {
            // 清理测试数据
            s.db.Exec("TRUNCATE TABLE users")
        }

        // ========== 步骤3:编写测试方法 ==========

        // 测试方法必须以Test开头
        func (s *UserRepositoryTestSuite) TestFindByID() {
            // 使用s.Assert()或s.Require()进行断言
            user, err := s.repo.FindByID(1)

            s.NoError(err)                              // 简写:s.Suite内置方法
            s.NotNil(user)
            s.Equal("Alice", user.Name)
            s.Equal("[email protected]", user.Email)
        }

        func (s *UserRepositoryTestSuite) TestFindAll() {
            users, err := s.repo.FindAll()

            s.NoError(err)
            s.Len(users, 2)
            s.Equal("Alice", users[0].Name)
            s.Equal("Bob", users[1].Name)
        }

        func (s *UserRepositoryTestSuite) TestCreate() {
            newUser := &User{
                Name:  "Charlie",
                Email: "[email protected]",
            }

            err := s.repo.Create(newUser)
            s.NoError(err)
            s.NotZero(newUser.ID)

            // 验证数据库中存在
            savedUser, err := s.repo.FindByID(newUser.ID)
            s.NoError(err)
            s.Equal("Charlie", savedUser.Name)
        }

        func (s *UserRepositoryTestSuite) TestUpdate() {
            user, _ := s.repo.FindByID(1)
            user.Email = "[email protected]"

            err := s.repo.Update(user)
            s.NoError(err)

            // 验证更新成功
            updated, _ := s.repo.FindByID(1)
            s.Equal("[email protected]", updated.Email)
        }

        func (s *UserRepositoryTestSuite) TestDelete() {
            err := s.repo.Delete(1)
            s.NoError(err)

            // 验证已删除
            user, err := s.repo.FindByID(1)
            s.Error(err)
            s.Nil(user)
        }

        // ========== 步骤4:运行测试套件 ==========

        // 测试入口函数
        func TestUserRepositoryTestSuite(t *testing.T) {
            suite.Run(t, new(UserRepositoryTestSuite))
        }

        // ========== 辅助定义 ==========

        type User struct {
            ID    int
            Name  string
            Email string
        }

        type UserRepository struct {
            db *sql.DB
        }

        func NewUserRepository(db *sql.DB) *UserRepository {
            return &UserRepository{db: db}
        }

        func (r *UserRepository) FindByID(id int) (*User, error) {
            user := &User{}
            err := r.db.QueryRow("SELECT id, name, email FROM users WHERE id = ?", id).
                Scan(&user.ID, &user.Name, &user.Email)
            if err != nil {
                return nil, err
            }
            return user, nil
        }

        func (r *UserRepository) FindAll() ([]*User, error) {
            rows, err := r.db.Query("SELECT id, name, email FROM users")
            if err != nil {
                return nil, err
            }
            defer rows.Close()

            var users []*User
            for rows.Next() {
                user := &User{}
                rows.Scan(&user.ID, &user.Name, &user.Email)
                users = append(users, user)
            }
            return users, nil
        }

        func (r *UserRepository) Create(user *User) error {
            result, err := r.db.Exec(
                "INSERT INTO users (name, email) VALUES (?, ?)",
                user.Name, user.Email,
            )
            if err != nil {
                return err
            }
            id, _ := result.LastInsertId()
            user.ID = int(id)
            return nil
        }

        func (r *UserRepository) Update(user *User) error {
            _, err := r.db.Exec(
                "UPDATE users SET name=?, email=? WHERE id=?",
                user.Name, user.Email, user.ID,
            )
            return err
        }

        func (r *UserRepository) Delete(id int) error {
            _, err := r.db.Exec("DELETE FROM users WHERE id = ?", id)
            return err
        }
        ---

02.生命周期钩子
    a.钩子执行顺序
        生命周期钩子按特定顺序执行:SetupSuite→SetupTest→Test方法→TearDownTest→下一个Test→TearDownSuite。SetupSuite和TearDownSuite每个套件只执行一次,SetupTest和TearDownTest每个测试方法都执行。
    b.钩子函数详解
        ---
        // 生命周期钩子详解
        package lifecycle

        import (
            "testing"
            "github.com/stretchr/testify/suite"
            "fmt"
        )

        // ========== 完整的生命周期演示 ==========

        type LifecycleTestSuite struct {
            suite.Suite
            counter int
        }

        // SetupSuite:套件级别初始化
        func (s *LifecycleTestSuite) SetupSuite() {
            fmt.Println("1. SetupSuite: 整个套件开始前执行(只执行1次)")
            // 适合:数据库连接、外部服务启动、全局配置加载
        }

        // SetupTest:测试级别初始化
        func (s *LifecycleTestSuite) SetupTest() {
            s.counter = 0
            fmt.Println("2. SetupTest: 每个测试方法前执行")
            // 适合:清空数据库、重置状态、准备测试数据
        }

        // BeforeTest:在SetupTest之后、测试方法之前执行
        func (s *LifecycleTestSuite) BeforeTest(suiteName, testName string) {
            fmt.Printf("3. BeforeTest: %s.%s 即将开始\n", suiteName, testName)
            // 适合:记录日志、设置上下文、特定测试的准备
        }

        // 测试方法1
        func (s *LifecycleTestSuite) TestExample1() {
            fmt.Println("4. TestExample1: 测试方法执行")
            s.counter++
            s.Equal(1, s.counter)
        }

        // 测试方法2
        func (s *LifecycleTestSuite) TestExample2() {
            fmt.Println("4. TestExample2: 测试方法执行")
            s.counter++
            s.Equal(1, s.counter)  // counter在SetupTest中重置为0
        }

        // AfterTest:在测试方法之后、TearDownTest之前执行
        func (s *LifecycleTestSuite) AfterTest(suiteName, testName string) {
            fmt.Printf("5. AfterTest: %s.%s 已完成\n", suiteName, testName)
            // 适合:收集测试指标、验证后置条件
        }

        // TearDownTest:测试级别清理
        func (s *LifecycleTestSuite) TearDownTest() {
            fmt.Println("6. TearDownTest: 每个测试方法后执行")
            // 适合:清理测试数据、释放测试资源
        }

        // TearDownSuite:套件级别清理
        func (s *LifecycleTestSuite) TearDownSuite() {
            fmt.Println("7. TearDownSuite: 整个套件结束后执行(只执行1次)")
            // 适合:关闭数据库连接、停止外部服务、清理全局资源
        }

        func TestLifecycleTestSuite(t *testing.T) {
            suite.Run(t, new(LifecycleTestSuite))
        }

        /*
        输出示例:
        1. SetupSuite: 整个套件开始前执行(只执行1次)
        2. SetupTest: 每个测试方法前执行
        3. BeforeTest: LifecycleTestSuite.TestExample1 即将开始
        4. TestExample1: 测试方法执行
        5. AfterTest: LifecycleTestSuite.TestExample1 已完成
        6. TearDownTest: 每个测试方法后执行
        2. SetupTest: 每个测试方法前执行
        3. BeforeTest: LifecycleTestSuite.TestExample2 即将开始
        4. TestExample2: 测试方法执行
        5. AfterTest: LifecycleTestSuite.TestExample2 已完成
        6. TearDownTest: 每个测试方法后执行
        7. TearDownSuite: 整个套件结束后执行(只执行1次)
        */

        // ========== 实战示例:数据库测试套件 ==========

        type DatabaseTestSuite struct {
            suite.Suite
            db       *sql.DB
            tx       *sql.Tx
            cleanups []func()
        }

        func (s *DatabaseTestSuite) SetupSuite() {
            // 连接测试数据库
            var err error
            s.db, err = sql.Open("postgres", "postgres://localhost/testdb")
            s.Require().NoError(err)

            // 运行迁移
            s.runMigrations()
        }

        func (s *DatabaseTestSuite) SetupTest() {
            // 每个测试使用独立事务
            var err error
            s.tx, err = s.db.Begin()
            s.Require().NoError(err)

            // 清空所有表
            s.tx.Exec("TRUNCATE TABLE users CASCADE")
            s.tx.Exec("TRUNCATE TABLE orders CASCADE")

            // 重置清理函数列表
            s.cleanups = nil
        }

        func (s *DatabaseTestSuite) TearDownTest() {
            // 回滚事务(隔离测试)
            if s.tx != nil {
                s.tx.Rollback()
            }

            // 执行注册的清理函数
            for i := len(s.cleanups) - 1; i >= 0; i-- {
                s.cleanups[i]()
            }
        }

        func (s *DatabaseTestSuite) TearDownSuite() {
            // 关闭数据库连接
            if s.db != nil {
                s.db.Close()
            }
        }

        // 辅助方法:注册清理函数
        func (s *DatabaseTestSuite) AddCleanup(fn func()) {
            s.cleanups = append(s.cleanups, fn)
        }

        func (s *DatabaseTestSuite) runMigrations() {
            // 创建表结构
        }

        // ========== 实战示例:HTTP服务测试套件 ==========

        type HTTPServiceTestSuite struct {
            suite.Suite
            server *httptest.Server
            client *http.Client
        }

        func (s *HTTPServiceTestSuite) SetupSuite() {
            // 启动测试服务器
            handler := NewHTTPHandler()
            s.server = httptest.NewServer(handler)

            // 创建HTTP客户端
            s.client = &http.Client{
                Timeout: 10 * time.Second,
            }
        }

        func (s *HTTPServiceTestSuite) TearDownSuite() {
            // 关闭测试服务器
            if s.server != nil {
                s.server.Close()
            }
        }

        func (s *HTTPServiceTestSuite) SetupTest() {
            // 每个测试前清理session/cookie
            jar, _ := cookiejar.New(nil)
            s.client.Jar = jar
        }

        func NewHTTPHandler() http.Handler {
            return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                w.WriteHeader(http.StatusOK)
            })
        }
        ---

03.子测试支持
    a.Run方法
        suite支持使用T().Run创建子测试,可以在套件内部进一步组织测试。子测试可以并行执行,提高测试效率。子测试有独立的名称,便于识别失败的具体场景。
    b.子测试示例
        ---
        // 子测试支持示例
        package subtest

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

        type SubTestSuite struct {
            suite.Suite
        }

        // 使用子测试组织多个场景
        func (s *SubTestSuite) TestUserValidation() {
            // 场景1:有效用户
            s.Run("ValidUser", func() {
                user := &User{Name: "Alice", Email: "[email protected]"}
                err := ValidateUser(user)
                s.NoError(err)
            })

            // 场景2:空名称
            s.Run("EmptyName", func() {
                user := &User{Name: "", Email: "[email protected]"}
                err := ValidateUser(user)
                s.Error(err)
                s.Contains(err.Error(), "name")
            })

            // 场景3:无效邮箱
            s.Run("InvalidEmail", func() {
                user := &User{Name: "Alice", Email: "invalid"}
                err := ValidateUser(user)
                s.Error(err)
                s.Contains(err.Error(), "email")
            })
        }

        // 表格驱动测试与子测试结合
        func (s *SubTestSuite) TestCalculator() {
            tests := []struct {
                name     string
                a, b     int
                expected int
            }{
                {"Positive", 2, 3, 5},
                {"Negative", -1, -2, -3},
                {"Zero", 0, 5, 5},
                {"Mixed", -3, 5, 2},
            }

            for _, tt := range tests {
                s.Run(tt.name, func() {
                    result := Add(tt.a, tt.b)
                    s.Equal(tt.expected, result)
                })
            }
        }

        func TestSubTestSuite(t *testing.T) {
            suite.Run(t, new(SubTestSuite))
        }

        type User struct {
            Name  string
            Email string
        }

        func ValidateUser(user *User) error {
            if user.Name == "" {
                return errors.New("name is required")
            }
            if !strings.Contains(user.Email, "@") {
                return errors.New("invalid email")
            }
            return nil
        }

        func Add(a, b int) int {
            return a + b
        }
        ---

04.最佳实践
    a.资源管理
        在SetupSuite中初始化昂贵资源如数据库连接,在TearDownSuite中清理。在SetupTest中准备测试数据,在TearDownTest中清理。使用defer确保资源一定被释放。合理使用生命周期钩子可以避免资源泄漏和测试间干扰。
    b.测试隔离
        每个测试应该独立,不依赖其他测试的执行顺序或结果。使用事务回滚或数据清理确保测试间隔离。避免在测试间共享可变状态。suite的SetupTest/TearDownTest机制有助于实现测试隔离。
    c.命名规范
        测试套件名称以TestSuite结尾,如UserRepositoryTestSuite。测试方法名称以Test开头,清晰描述测试内容,如TestCreateUser。生命周期钩子使用标准名称不要自定义。

2.6 http包

01.HTTP测试工具
    a.包设计目的
        testify的http包封装了Go标准库的httptest包,提供更便捷的HTTP测试断言。虽然http包在testify中使用频率不如assert和mock高,但对于Web应用和API服务测试仍然很有价值。它简化了HTTP请求和响应的测试代码。
    b.核心功能
        http包提供用于测试HTTP处理器的工具函数,可以创建测试请求、捕获响应、验证状态码和响应内容。与httptest.ResponseRecorder配合使用,可以完整地测试HTTP处理逻辑而无需启动真实服务器。
    c.使用场景
        http包适用于单元测试HTTP处理器函数,测试RESTful API端点,验证中间件行为,测试HTTP客户端代码。对于复杂的集成测试,可能需要配合httptest.Server使用。
    d.基础示例
        ---
        // http包基础示例
        package httpbasic

        import (
            "testing"
            "net/http"
            "net/http/httptest"
            "github.com/stretchr/testify/assert"
            "encoding/json"
        )

        // ========== HTTP处理器定义 ==========

        // 简单的GET处理器
        func HelloHandler(w http.ResponseWriter, r *http.Request) {
            w.Header().Set("Content-Type", "text/plain")
            w.WriteHeader(http.StatusOK)
            w.Write([]byte("Hello, World!"))
        }

        // JSON API处理器
        func UserHandler(w http.ResponseWriter, r *http.Request) {
            user := map[string]interface{}{
                "id":    1,
                "name":  "Alice",
                "email": "[email protected]",
            }

            w.Header().Set("Content-Type", "application/json")
            w.WriteHeader(http.StatusOK)
            json.NewEncoder(w).Encode(user)
        }

        // POST处理器
        func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
            if r.Method != http.MethodPOST {
                w.WriteHeader(http.StatusMethodNotAllowed)
                return
            }

            var user struct {
                Name  string `json:"name"`
                Email string `json:"email"`
            }

            err := json.NewDecoder(r.Body).Decode(&user)
            if err != nil {
                w.WriteHeader(http.StatusBadRequest)
                json.NewEncoder(w).Encode(map[string]string{"error": "Invalid JSON"})
                return
            }

            if user.Name == "" || user.Email == "" {
                w.WriteHeader(http.StatusBadRequest)
                json.NewEncoder(w).Encode(map[string]string{"error": "Name and email required"})
                return
            }

            response := map[string]interface{}{
                "id":    123,
                "name":  user.Name,
                "email": user.Email,
            }

            w.Header().Set("Content-Type", "application/json")
            w.WriteHeader(http.StatusCreated)
            json.NewEncoder(w).Encode(response)
        }

        // ========== 测试示例 ==========

        // 测试1:简单GET请求
        func TestHelloHandler(t *testing.T) {
            // 创建请求
            req, err := http.NewRequest("GET", "/hello", nil)
            assert.NoError(t, err)

            // 创建ResponseRecorder捕获响应
            rr := httptest.NewRecorder()

            // 调用处理器
            handler := http.HandlerFunc(HelloHandler)
            handler.ServeHTTP(rr, req)

            // 验证响应
            assert.Equal(t, http.StatusOK, rr.Code)
            assert.Equal(t, "text/plain", rr.Header().Get("Content-Type"))
            assert.Equal(t, "Hello, World!", rr.Body.String())
        }

        // 测试2:JSON响应
        func TestUserHandler(t *testing.T) {
            req, _ := http.NewRequest("GET", "/user/1", nil)
            rr := httptest.NewRecorder()

            handler := http.HandlerFunc(UserHandler)
            handler.ServeHTTP(rr, req)

            // 验证状态码和Content-Type
            assert.Equal(t, http.StatusOK, rr.Code)
            assert.Contains(t, rr.Header().Get("Content-Type"), "application/json")

            // 解析JSON响应
            var user map[string]interface{}
            err := json.Unmarshal(rr.Body.Bytes(), &user)
            assert.NoError(t, err)

            // 验证JSON内容
            assert.Equal(t, float64(1), user["id"])
            assert.Equal(t, "Alice", user["name"])
            assert.Equal(t, "[email protected]", user["email"])

            // 或使用JSONEq直接比较JSON字符串
            expected := `{"id":1,"name":"Alice","email":"[email protected]"}`
            assert.JSONEq(t, expected, rr.Body.String())
        }

        // 测试3:POST请求
        func TestCreateUserHandler(t *testing.T) {
            // 准备请求体
            requestBody := `{"name":"Bob","email":"[email protected]"}`
            req, _ := http.NewRequest("POST", "/users", strings.NewReader(requestBody))
            req.Header.Set("Content-Type", "application/json")

            rr := httptest.NewRecorder()
            handler := http.HandlerFunc(CreateUserHandler)
            handler.ServeHTTP(rr, req)

            // 验证响应
            assert.Equal(t, http.StatusCreated, rr.Code)

            var response map[string]interface{}
            json.Unmarshal(rr.Body.Bytes(), &response)

            assert.Equal(t, float64(123), response["id"])
            assert.Equal(t, "Bob", response["name"])
            assert.Equal(t, "[email protected]", response["email"])
        }

        // 测试4:错误处理
        func TestCreateUserHandler_InvalidJSON(t *testing.T) {
            // 无效的JSON
            req, _ := http.NewRequest("POST", "/users", strings.NewReader("invalid json"))
            req.Header.Set("Content-Type", "application/json")

            rr := httptest.NewRecorder()
            handler := http.HandlerFunc(CreateUserHandler)
            handler.ServeHTTP(rr, req)

            // 验证错误响应
            assert.Equal(t, http.StatusBadRequest, rr.Code)

            var errorResp map[string]string
            json.Unmarshal(rr.Body.Bytes(), &errorResp)
            assert.Equal(t, "Invalid JSON", errorResp["error"])
        }

        // 测试5:缺少必填字段
        func TestCreateUserHandler_MissingFields(t *testing.T) {
            requestBody := `{"name":"Bob"}`  // 缺少email
            req, _ := http.NewRequest("POST", "/users", strings.NewReader(requestBody))
            req.Header.Set("Content-Type", "application/json")

            rr := httptest.NewRecorder()
            handler := http.HandlerFunc(CreateUserHandler)
            handler.ServeHTTP(rr, req)

            assert.Equal(t, http.StatusBadRequest, rr.Code)
            assert.Contains(t, rr.Body.String(), "required")
        }

        // 测试6:方法不允许
        func TestCreateUserHandler_MethodNotAllowed(t *testing.T) {
            req, _ := http.NewRequest("GET", "/users", nil)

            rr := httptest.NewRecorder()
            handler := http.HandlerFunc(CreateUserHandler)
            handler.ServeHTTP(rr, req)

            assert.Equal(t, http.StatusMethodNotAllowed, rr.Code)
        }
        ---

02.中间件测试
    a.中间件概念
        中间件是包装HTTP处理器的函数,用于在请求处理前后执行额外逻辑,如认证、日志、CORS等。测试中间件需要验证它正确地修改请求或响应,并调用下一个处理器。
    b.中间件测试示例
        ---
        // 中间件测试示例
        package middleware

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

        // ========== 中间件定义 ==========

        // 日志中间件
        func LoggingMiddleware(next http.Handler) http.Handler {
            return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                // 记录请求信息
                log.Printf("%s %s", r.Method, r.URL.Path)

                // 调用下一个处理器
                next.ServeHTTP(w, r)
            })
        }

        // 认证中间件
        func AuthMiddleware(next http.Handler) http.Handler {
            return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                // 检查认证token
                token := r.Header.Get("Authorization")
                if token == "" {
                    w.WriteHeader(http.StatusUnauthorized)
                    w.Write([]byte("Unauthorized"))
                    return
                }

                if token != "valid-token" {
                    w.WriteHeader(http.StatusForbidden)
                    w.Write([]byte("Forbidden"))
                    return
                }

                // 认证通过,调用下一个处理器
                next.ServeHTTP(w, r)
            })
        }

        // CORS中间件
        func CORSMiddleware(next http.Handler) http.Handler {
            return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                // 设置CORS头
                w.Header().Set("Access-Control-Allow-Origin", "*")
                w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
                w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")

                // 处理预检请求
                if r.Method == "OPTIONS" {
                    w.WriteHeader(http.StatusOK)
                    return
                }

                next.ServeHTTP(w, r)
            })
        }

        // ========== 中间件测试 ==========

        // 测试:日志中间件
        func TestLoggingMiddleware(t *testing.T) {
            // 创建测试处理器
            handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                w.WriteHeader(http.StatusOK)
                w.Write([]byte("OK"))
            })

            // 应用中间件
            wrappedHandler := LoggingMiddleware(handler)

            req, _ := http.NewRequest("GET", "/test", nil)
            rr := httptest.NewRecorder()

            wrappedHandler.ServeHTTP(rr, req)

            // 验证请求仍然正常处理
            assert.Equal(t, http.StatusOK, rr.Code)
            assert.Equal(t, "OK", rr.Body.String())
        }

        // 测试:认证中间件-无token
        func TestAuthMiddleware_NoToken(t *testing.T) {
            handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                w.Write([]byte("Protected"))
            })

            wrappedHandler := AuthMiddleware(handler)

            req, _ := http.NewRequest("GET", "/protected", nil)
            rr := httptest.NewRecorder()

            wrappedHandler.ServeHTTP(rr, req)

            // 验证返回401
            assert.Equal(t, http.StatusUnauthorized, rr.Code)
            assert.Equal(t, "Unauthorized", rr.Body.String())
        }

        // 测试:认证中间件-无效token
        func TestAuthMiddleware_InvalidToken(t *testing.T) {
            handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                w.Write([]byte("Protected"))
            })

            wrappedHandler := AuthMiddleware(handler)

            req, _ := http.NewRequest("GET", "/protected", nil)
            req.Header.Set("Authorization", "invalid-token")
            rr := httptest.NewRecorder()

            wrappedHandler.ServeHTTP(rr, req)

            assert.Equal(t, http.StatusForbidden, rr.Code)
            assert.Equal(t, "Forbidden", rr.Body.String())
        }

        // 测试:认证中间件-有效token
        func TestAuthMiddleware_ValidToken(t *testing.T) {
            handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                w.Write([]byte("Protected Content"))
            })

            wrappedHandler := AuthMiddleware(handler)

            req, _ := http.NewRequest("GET", "/protected", nil)
            req.Header.Set("Authorization", "valid-token")
            rr := httptest.NewRecorder()

            wrappedHandler.ServeHTTP(rr, req)

            // 验证请求通过
            assert.Equal(t, http.StatusOK, rr.Code)
            assert.Equal(t, "Protected Content", rr.Body.String())
        }

        // 测试:CORS中间件
        func TestCORSMiddleware(t *testing.T) {
            handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                w.Write([]byte("OK"))
            })

            wrappedHandler := CORSMiddleware(handler)

            req, _ := http.NewRequest("GET", "/api", nil)
            rr := httptest.NewRecorder()

            wrappedHandler.ServeHTTP(rr, req)

            // 验证CORS头已设置
            assert.Equal(t, "*", rr.Header().Get("Access-Control-Allow-Origin"))
            assert.Equal(t, "GET, POST, PUT, DELETE", rr.Header().Get("Access-Control-Allow-Methods"))
            assert.Contains(t, rr.Header().Get("Access-Control-Allow-Headers"), "Authorization")
        }

        // 测试:CORS预检请求
        func TestCORSMiddleware_Preflight(t *testing.T) {
            handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                w.Write([]byte("Should not reach here"))
            })

            wrappedHandler := CORSMiddleware(handler)

            req, _ := http.NewRequest("OPTIONS", "/api", nil)
            rr := httptest.NewRecorder()

            wrappedHandler.ServeHTTP(rr, req)

            // 验证预检请求返回200且不调用下一个处理器
            assert.Equal(t, http.StatusOK, rr.Code)
            assert.Empty(t, rr.Body.String())
        }

        // 测试:多个中间件链
        func TestMiddlewareChain(t *testing.T) {
            finalHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                w.Write([]byte("Final"))
            })

            // 组合多个中间件
            handler := LoggingMiddleware(
                CORSMiddleware(
                    AuthMiddleware(finalHandler),
                ),
            )

            req, _ := http.NewRequest("GET", "/api", nil)
            req.Header.Set("Authorization", "valid-token")
            rr := httptest.NewRecorder()

            handler.ServeHTTP(rr, req)

            // 验证所有中间件都生效
            assert.Equal(t, http.StatusOK, rr.Code)
            assert.Equal(t, "Final", rr.Body.String())
            assert.Equal(t, "*", rr.Header().Get("Access-Control-Allow-Origin"))
        }
        ---

03.httptest.Server使用
    a.测试服务器
        httptest.Server可以启动一个真实的HTTP服务器用于测试,适合集成测试场景。相比ResponseRecorder,Server提供完整的HTTP栈,可以测试完整的请求响应流程。
    b.Server测试示例
        ---
        // httptest.Server测试示例
        package server

        import (
            "testing"
            "net/http"
            "net/http/httptest"
            "github.com/stretchr/testify/assert"
            "io"
        )

        func TestWithHTTPServer(t *testing.T) {
            // 创建测试服务器
            server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                w.WriteHeader(http.StatusOK)
                w.Write([]byte("Test Server"))
            }))
            defer server.Close()

            // 发送真实HTTP请求
            resp, err := http.Get(server.URL)
            assert.NoError(t, err)
            defer resp.Body.Close()

            // 验证响应
            assert.Equal(t, http.StatusOK, resp.StatusCode)

            body, _ := io.ReadAll(resp.Body)
            assert.Equal(t, "Test Server", string(body))
        }

        // 测试:带路由的服务器
        func TestServerWithRouter(t *testing.T) {
            mux := http.NewServeMux()
            mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
                w.Write([]byte("Users"))
            })
            mux.HandleFunc("/posts", func(w http.ResponseWriter, r *http.Request) {
                w.Write([]byte("Posts"))
            })

            server := httptest.NewServer(mux)
            defer server.Close()

            // 测试/users
            resp1, _ := http.Get(server.URL + "/users")
            body1, _ := io.ReadAll(resp1.Body)
            assert.Equal(t, "Users", string(body1))
            resp1.Body.Close()

            // 测试/posts
            resp2, _ := http.Get(server.URL + "/posts")
            body2, _ := io.ReadAll(resp2.Body)
            assert.Equal(t, "Posts", string(body2))
            resp2.Body.Close()
        }
        ---

04.最佳实践
    a.选择合适的测试工具
        单元测试HTTP处理器使用httptest.ResponseRecorder,快速且无需网络。集成测试使用httptest.Server,提供完整HTTP栈。端到端测试考虑使用真实服务器或Docker容器。
    b.测试组织
        为每个HTTP端点编写独立测试,包括正常场景和异常场景。测试不同的HTTP方法、请求头、查询参数、请求体。验证状态码、响应头、响应体的正确性。

2.7 附:包选择建议

01.选择决策树
    a.决策流程
        选择testify包时应该根据测试场景和需求进行决策。首先确定是否需要断言功能,然后判断断言失败是否应该立即终止测试,接着考虑是否需要mock依赖,最后判断是否需要结构化的测试组织。这个决策流程可以帮助快速选择合适的包组合。
    b.决策树图示
        ---
        // testify包选择决策树
        package decision

        /*
        ┌─────────────────────────────────────────┐
        │     开始:我需要编写测试                  │
        └──────────────┬──────────────────────────┘
                       │
                       ▼
        ┌─────────────────────────────────────────┐
        │  是否需要断言功能?                       │
        ├─────────────────────────────────────────┤
        │  Yes  →  继续                            │
        │  No   →  使用标准库testing包             │
        └──────────────┬──────────────────────────┘
                       │ Yes
                       ▼
        ┌─────────────────────────────────────────┐
        │  是否是前置条件检查?                     │
        │  (失败应立即终止测试)                   │
        ├─────────────────────────────────────────┤
        │  Yes  →  使用require包                   │
        │  No   →  使用assert包                    │
        └──────────────┬──────────────────────────┘
                       │
                       ▼
        ┌─────────────────────────────────────────┐
        │  是否需要隔离外部依赖?                   │
        ├─────────────────────────────────────────┤
        │  Yes  →  添加mock包                      │
        │  No   →  跳过                            │
        └──────────────┬──────────────────────────┘
                       │
                       ▼
        ┌─────────────────────────────────────────┐
        │  是否有复杂的初始化和清理?               │
        │  (多个相关测试需要共享资源)             │
        ├─────────────────────────────────────────┤
        │  Yes  →  使用suite包                     │
        │  No   →  使用函数式测试                  │
        └──────────────┬──────────────────────────┘
                       │
                       ▼
        ┌─────────────────────────────────────────┐
        │  是否测试HTTP处理器?                    │
        ├─────────────────────────────────────────┤
        │  Yes  →  考虑使用http包(可选)          │
        │  No   →  完成                            │
        └─────────────────────────────────────────┘


        ========== 典型场景包组合 ==========

        场景1:简单单元测试
        ├─ assert包:验证函数返回值
        └─ 推荐指数:★★★★★

        场景2:带资源初始化的单元测试
        ├─ require包:检查资源初始化
        ├─ assert包:验证业务逻辑
        └─ 推荐指数:★★★★★

        场景3:需要隔离依赖的单元测试
        ├─ require包:检查mock对象创建
        ├─ mock包:模拟外部依赖
        ├─ assert包:验证业务逻辑
        └─ 推荐指数:★★★★★

        场景4:数据库集成测试
        ├─ suite包:管理数据库连接和事务
        ├─ require包:检查数据库操作
        ├─ assert包:验证查询结果
        └─ 推荐指数:★★★★☆

        场景5:HTTP API测试
        ├─ require包:检查请求创建
        ├─ assert包:验证响应
        ├─ http包:简化HTTP测试(可选)
        └─ 推荐指数:★★★★☆

        场景6:完整的端到端测试
        ├─ suite包:管理测试环境
        ├─ require包:检查前置条件
        ├─ mock包:模拟外部服务
        ├─ assert包:验证结果
        └─ 推荐指数:★★★★★
        */

        // 示例:简单单元测试(只用assert)
        func TestSimpleUnit(t *testing.T) {
            a := assert.New(t)

            result := Add(2, 3)
            a.Equal(5, result)
        }

        // 示例:带资源初始化(require + assert)
        func TestWithResource(t *testing.T) {
            r := require.New(t)
            a := assert.New(t)

            // require:前置条件
            db := ConnectDB()
            r.NotNil(db)
            defer db.Close()

            // assert:业务逻辑
            users, err := db.QueryUsers()
            a.NoError(err)
            a.Len(users, 3)
        }

        // 示例:隔离依赖(require + mock + assert)
        func TestWithMock(t *testing.T) {
            r := require.New(t)
            a := assert.New(t)

            // mock:模拟依赖
            mockRepo := new(MockRepository)
            mockRepo.On("Get", 1).Return("data", nil)

            // require:创建服务
            service := NewService(mockRepo)
            r.NotNil(service)

            // assert:验证行为
            result, err := service.Process(1)
            a.NoError(err)
            a.Equal("data", result)

            mockRepo.AssertExpectations(t)
        }

        func Add(a, b int) int { return a + b }

        type DB struct{}
        func ConnectDB() *DB { return &DB{} }
        func (db *DB) Close() {}
        func (db *DB) QueryUsers() ([]string, error) { return []string{"a", "b", "c"}, nil }

        type MockRepository struct{ mock.Mock }
        func (m *MockRepository) Get(id int) (string, error) {
            args := m.Called(id)
            return args.String(0), args.Error(1)
        }

        type Service struct{}
        func NewService(repo *MockRepository) *Service { return &Service{} }
        func (s *Service) Process(id int) (string, error) { return "data", nil }
        ---

02.常见场景建议
    a.纯逻辑函数测试
        对于不依赖外部资源的纯函数,使用assert包即可。编写简单直接的测试,验证输入输出关系。不需要mock和suite,保持测试简单。适合工具函数、算法实现、数据转换等场景。
    b.业务逻辑层测试
        业务逻辑层通常依赖数据访问层和外部服务,需要使用mock隔离依赖。使用require检查mock对象创建,使用assert验证业务逻辑,使用mock包模拟依赖。这是最常见的测试场景。
    c.数据访问层测试
        数据访问层测试需要真实数据库连接,使用suite管理数据库连接和事务。在SetupSuite中连接数据库,在SetupTest中准备测试数据,在TearDownTest中清理数据。使用require检查数据库操作,使用assert验证查询结果。
    d.场景示例
        ---
        // 常见场景包选择示例
        package scenarios

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

        // ========== 场景1:纯逻辑函数(只用assert)==========

        func TestPureFunction(t *testing.T) {
            a := assert.New(t)

            // 测试字符串处理函数
            a.Equal("HELLO", ToUpper("hello"))
            a.Equal("hello", ToLower("HELLO"))

            // 测试数学计算函数
            a.Equal(15, Sum([]int{1, 2, 3, 4, 5}))
            a.Equal(3.0, Average([]float64{1, 2, 3, 4, 5}))

            // 测试数据验证函数
            a.True(IsValidEmail("[email protected]"))
            a.False(IsValidEmail("invalid"))
        }

        // ========== 场景2:业务逻辑层(require + mock + assert)==========

        func TestBusinessLogic(t *testing.T) {
            r := require.New(t)
            a := assert.New(t)

            // Mock依赖
            mockUserRepo := new(MockUserRepository)
            mockOrderRepo := new(MockOrderRepository)
            mockNotifier := new(MockNotifier)

            // 设置期望
            user := &User{ID: 1, Name: "Alice", Email: "[email protected]"}
            mockUserRepo.On("FindByID", 1).Return(user, nil)
            mockOrderRepo.On("Create", mock.Anything).Return(nil)
            mockNotifier.On("Send", user.Email, mock.Anything).Return(nil)

            // 创建服务(使用require确保成功)
            service := NewOrderService(mockUserRepo, mockOrderRepo, mockNotifier)
            r.NotNil(service)

            // 测试业务逻辑(使用assert)
            order, err := service.CreateOrder(1, []OrderItem{{ProductID: 1, Quantity: 2}})
            a.NoError(err)
            a.NotNil(order)
            a.Equal(1, order.UserID)

            // 验证mock调用
            mockUserRepo.AssertExpectations(t)
            mockOrderRepo.AssertExpectations(t)
            mockNotifier.AssertExpectations(t)
        }

        // ========== 场景3:数据访问层(suite + require + assert)==========

        type RepositoryTestSuite struct {
            suite.Suite
            db   *sql.DB
            repo *UserRepository
        }

        func (s *RepositoryTestSuite) SetupSuite() {
            var err error
            s.db, err = sql.Open("postgres", "postgres://localhost/testdb")
            s.Require().NoError(err)
        }

        func (s *RepositoryTestSuite) SetupTest() {
            s.db.Exec("TRUNCATE TABLE users")
            s.db.Exec("INSERT INTO users (name, email) VALUES ('Alice', '[email protected]')")
            s.repo = NewUserRepository(s.db)
        }

        func (s *RepositoryTestSuite) TestFindByID() {
            user, err := s.repo.FindByID(1)

            s.NoError(err)
            s.NotNil(user)
            s.Equal("Alice", user.Name)
        }

        func (s *RepositoryTestSuite) TestCreate() {
            newUser := &User{Name: "Bob", Email: "[email protected]"}
            err := s.repo.Create(newUser)

            s.NoError(err)
            s.NotZero(newUser.ID)
        }

        func (s *RepositoryTestSuite) TearDownSuite() {
            if s.db != nil {
                s.db.Close()
            }
        }

        func TestRepositoryTestSuite(t *testing.T) {
            suite.Run(t, new(RepositoryTestSuite))
        }

        // ========== 场景4:HTTP API(require + assert)==========

        func TestHTTPAPI(t *testing.T) {
            r := require.New(t)
            a := assert.New(t)

            // 创建请求
            requestBody := `{"name":"Alice","email":"[email protected]"}`
            req, err := http.NewRequest("POST", "/users", strings.NewReader(requestBody))
            r.NoError(err)
            req.Header.Set("Content-Type", "application/json")

            // 执行请求
            rr := httptest.NewRecorder()
            handler := UserHandler()
            handler.ServeHTTP(rr, req)

            // 验证响应
            a.Equal(http.StatusCreated, rr.Code)
            a.Contains(rr.Header().Get("Content-Type"), "application/json")

            var response map[string]interface{}
            json.Unmarshal(rr.Body.Bytes(), &response)
            a.Equal("Alice", response["name"])
        }

        // 辅助函数和类型
        func ToUpper(s string) string { return strings.ToUpper(s) }
        func ToLower(s string) string { return strings.ToLower(s) }
        func Sum(nums []int) int {
            sum := 0
            for _, n := range nums {
                sum += n
            }
            return sum
        }
        func Average(nums []float64) float64 {
            if len(nums) == 0 {
                return 0
            }
            sum := 0.0
            for _, n := range nums {
                sum += n
            }
            return sum / float64(len(nums))
        }
        func IsValidEmail(email string) bool { return strings.Contains(email, "@") }

        type User struct {
            ID    int
            Name  string
            Email string
        }

        type OrderItem struct {
            ProductID int
            Quantity  int
        }

        type Order struct {
            UserID int
            Items  []OrderItem
        }

        type MockUserRepository struct{ mock.Mock }
        func (m *MockUserRepository) FindByID(id int) (*User, error) {
            args := m.Called(id)
            if args.Get(0) == nil {
                return nil, args.Error(1)
            }
            return args.Get(0).(*User), args.Error(1)
        }

        type MockOrderRepository struct{ mock.Mock }
        func (m *MockOrderRepository) Create(order *Order) error {
            args := m.Called(order)
            return args.Error(0)
        }

        type MockNotifier struct{ mock.Mock }
        func (m *MockNotifier) Send(email, message string) error {
            args := m.Called(email, message)
            return args.Error(0)
        }

        type OrderService struct{}
        func NewOrderService(userRepo *MockUserRepository, orderRepo *MockOrderRepository, notifier *MockNotifier) *OrderService {
            return &OrderService{}
        }
        func (s *OrderService) CreateOrder(userID int, items []OrderItem) (*Order, error) {
            return &Order{UserID: userID, Items: items}, nil
        }

        type UserRepository struct{ db *sql.DB }
        func NewUserRepository(db *sql.DB) *UserRepository { return &UserRepository{db: db} }
        func (r *UserRepository) FindByID(id int) (*User, error) { return &User{ID: id, Name: "Alice", Email: "[email protected]"}, nil }
        func (r *UserRepository) Create(user *User) error { user.ID = 1; return nil }

        func UserHandler() http.Handler {
            return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                w.Header().Set("Content-Type", "application/json")
                w.WriteHeader(http.StatusCreated)
                w.Write([]byte(`{"name":"Alice"}`))
            })
        }
        ---

03.性能考虑
    a.包开销
        assert和require包由于使用反射进行深度比较,有一定性能开销,但在测试场景下完全可以接受。mock包维护期望列表和调用记录,有少量内存开销。suite包使用反射扫描测试方法,启动略慢但执行性能正常。
    b.优化建议
        避免在测试中进行不必要的复杂断言,简单的相等检查即可。合理使用suite的SetupSuite和SetupTest,避免重复初始化。对于性能测试基准测试,考虑使用标准库而非testify以减少干扰。
    c.性能对比
        ---
        // 性能对比基准测试
        package performance

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

        // 标准库断言
        func BenchmarkStandardAssertion(b *testing.B) {
            for i := 0; i < b.N; i++ {
                result := Add(2, 3)
                if result != 5 {
                    b.Errorf("expected 5, got %d", result)
                }
            }
        }

        // testify断言
        func BenchmarkTestifyAssertion(b *testing.B) {
            for i := 0; i < b.N; i++ {
                result := Add(2, 3)
                assert.Equal(b, 5, result)
            }
        }

        /*
        结果参考:
        BenchmarkStandardAssertion-8    100000000    12 ns/op
        BenchmarkTestifyAssertion-8      20000000    85 ns/op

        结论:testify约慢7倍,但绝对时间仍在纳秒级
        对于测试场景,这个开销完全可以接受
        */

        func Add(a, b int) int { return a + b }
        ---

04.团队协作
    a.统一规范
        团队应该建立统一的testify使用规范,明确assert vs require的使用场景。统一mock对象的命名和组织方式。统一suite的生命周期钩子使用模式。代码审查时检查测试代码的规范性。
    b.最佳实践
        在项目README中说明测试规范,提供测试示例代码。新成员入职时培训testify使用方法。建立测试模板,降低编写测试的门槛。定期Review测试代码质量,持续改进。
    c.规范示例
        ---
        // 团队测试规范示例
        package standards

        /*
        ========== 项目测试规范 ==========

        1. 包选择规则
           - 前置条件:require包
           - 多个检查:assert包
           - 外部依赖:mock包
           - 复杂初始化:suite包

        2. 命名规范
           - Mock对象:Mock + 接口名(MockUserRepository)
           - 测试套件:类名 + TestSuite(UserServiceTestSuite)
           - 测试函数:Test + 功能描述(TestCreateUser)
           - 子测试:使用Run + 描述性名称

        3. 文件组织
           - 测试文件与源文件同目录
           - 命名:源文件_test.go
           - Mock对象可以单独放在mocks目录

        4. 测试结构
           - 使用AAA模式:Arrange-Act-Assert
           - 每个测试只测试一个行为
           - 测试名称清晰描述测试内容

        5. 断言风格
           - 优先使用New(t)创建断言对象
           - 断言失败提供有意义的消息
           - 相关断言组织在一起

        6. Mock使用
           - Mock对象在测试函数内创建
           - 设置期望后立即使用
           - 测试结束前验证期望

        7. Suite使用
           - 合理使用生命周期钩子
           - 确保测试隔离
           - 避免测试间共享可变状态

        ========== 代码示例 ==========
        */

        // 标准测试函数结构
        func TestStandardStructure(t *testing.T) {
            // Arrange:准备测试数据和mock
            r := require.New(t)
            a := assert.New(t)

            mockRepo := new(MockRepository)
            mockRepo.On("Get", 1).Return("data", nil)

            service := NewService(mockRepo)
            r.NotNil(service)

            // Act:执行被测方法
            result, err := service.Process(1)

            // Assert:验证结果
            a.NoError(err)
            a.Equal("data", result)
            mockRepo.AssertExpectations(t)
        }

        // 使用子测试组织多个场景
        func TestWithSubtests(t *testing.T) {
            t.Run("成功场景", func(t *testing.T) {
                // 测试代码
            })

            t.Run("错误场景", func(t *testing.T) {
                // 测试代码
            })

            t.Run("边界场景", func(t *testing.T) {
                // 测试代码
            })
        }

        type MockRepository struct{ mock.Mock }
        func (m *MockRepository) Get(id int) (string, error) {
            args := m.Called(id)
            return args.String(0), args.Error(1)
        }

        type Service struct{}
        func NewService(repo *MockRepository) *Service { return &Service{} }
        func (s *Service) Process(id int) (string, error) { return "data", nil }
        ---

3 断言系统详解

3.1 基础断言

01.Equal断言
    a.功能说明
        Equal是testify中最常用的断言方法,用于检查两个值是否深度相等。它使用reflect.DeepEqual进行比较,支持所有Go类型包括基本类型、结构体、切片、map、指针等。Equal不仅比较值,还递归比较嵌套的数据结构,确保完全一致。
    b.使用场景
        Equal适用于几乎所有的相等性检查场景。基本类型比较如整数、字符串、布尔值,复杂类型比较如结构体、切片、map,以及指针指向的值比较。Equal是编写测试时的首选断言方法。
    c.代码示例
        ---
        // Equal断言详解
        package equal

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

        func TestEqualBasicTypes(t *testing.T) {
            a := assert.New(t)

            // 整数比较
            a.Equal(42, 40+2)
            a.Equal(int32(100), int32(100))
            a.Equal(int64(1000), int64(1000))

            // 浮点数比较(注意精度问题)
            a.Equal(3.14, 3.14)
            a.Equal(float32(2.5), float32(2.5))

            // 字符串比较
            a.Equal("hello", "hel"+"lo")
            a.Equal("", "")

            // 布尔值比较
            a.Equal(true, true)
            a.Equal(false, !true)

            // 字节切片比较
            a.Equal([]byte("hello"), []byte("hello"))
        }

        func TestEqualStructs(t *testing.T) {
            a := assert.New(t)

            type Person struct {
                Name string
                Age  int
            }

            // 结构体比较
            p1 := Person{Name: "Alice", Age: 25}
            p2 := Person{Name: "Alice", Age: 25}
            a.Equal(p1, p2)

            // 结构体指针比较(比较值,不是指针地址)
            a.Equal(&p1, &p2)

            // 嵌套结构体比较
            type Address struct {
                City    string
                Country string
            }

            type Employee struct {
                Person  Person
                Address Address
                Salary  float64
            }

            e1 := Employee{
                Person:  Person{Name: "Bob", Age: 30},
                Address: Address{City: "Beijing", Country: "China"},
                Salary:  50000.0,
            }

            e2 := Employee{
                Person:  Person{Name: "Bob", Age: 30},
                Address: Address{City: "Beijing", Country: "China"},
                Salary:  50000.0,
            }

            a.Equal(e1, e2)
        }

        func TestEqualSlices(t *testing.T) {
            a := assert.New(t)

            // 切片比较
            a.Equal([]int{1, 2, 3}, []int{1, 2, 3})
            a.Equal([]string{"a", "b", "c"}, []string{"a", "b", "c"})

            // 空切片vs nil切片(不相等)
            var nilSlice []int
            emptySlice := []int{}
            // a.Equal(nilSlice, emptySlice)  // 失败:nil != []
            a.Nil(nilSlice)
            a.NotNil(emptySlice)

            // 嵌套切片
            nested := [][]int{{1, 2}, {3, 4}, {5, 6}}
            expected := [][]int{{1, 2}, {3, 4}, {5, 6}}
            a.Equal(expected, nested)

            // 结构体切片
            type User struct {
                ID   int
                Name string
            }

            users1 := []User{
                {ID: 1, Name: "Alice"},
                {ID: 2, Name: "Bob"},
            }

            users2 := []User{
                {ID: 1, Name: "Alice"},
                {ID: 2, Name: "Bob"},
            }

            a.Equal(users1, users2)
        }

        func TestEqualMaps(t *testing.T) {
            a := assert.New(t)

            // map比较
            m1 := map[string]int{"a": 1, "b": 2, "c": 3}
            m2 := map[string]int{"a": 1, "b": 2, "c": 3}
            a.Equal(m1, m2)

            // map顺序无关
            m3 := map[string]int{"c": 3, "a": 1, "b": 2}
            a.Equal(m1, m3)

            // 嵌套map
            nested1 := map[string]map[string]int{
                "group1": {"a": 1, "b": 2},
                "group2": {"c": 3, "d": 4},
            }

            nested2 := map[string]map[string]int{
                "group1": {"a": 1, "b": 2},
                "group2": {"c": 3, "d": 4},
            }

            a.Equal(nested1, nested2)

            // map值为结构体
            type Config struct {
                Host string
                Port int
            }

            configs1 := map[string]Config{
                "db":    {Host: "localhost", Port: 3306},
                "redis": {Host: "localhost", Port: 6379},
            }

            configs2 := map[string]Config{
                "db":    {Host: "localhost", Port: 3306},
                "redis": {Host: "localhost", Port: 6379},
            }

            a.Equal(configs1, configs2)
        }

        func TestEqualPointers(t *testing.T) {
            a := assert.New(t)

            // 指针比较(比较指向的值)
            x := 42
            y := 42
            a.Equal(&x, &y)  // 通过:值相等

            // 指针指向相同对象
            p1 := &x
            p2 := &x
            a.Same(p1, p2)   // 使用Same检查指针相同

            // nil指针
            var p3 *int
            var p4 *int
            a.Equal(p3, p4)  // 都是nil
        }
        ---

02.NotEqual断言
    a.功能说明
        NotEqual断言用于检查两个值不相等,是Equal的反向断言。在测试负面场景时非常有用,如验证两个对象确实不同,或验证修改操作确实改变了值。NotEqual同样使用深度比较,确保值在各个层级都不同。
    b.使用场景
        NotEqual适用于验证值确实发生了变化,测试互斥条件,验证不同的输入产生不同的输出。在测试数据修改、状态变更、配置差异等场景中特别有用。
    c.代码示例
        ---
        // NotEqual断言详解
        package notequal

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

        func TestNotEqualBasics(t *testing.T) {
            a := assert.New(t)

            // 基本类型不相等
            a.NotEqual(1, 2)
            a.NotEqual("hello", "world")
            a.NotEqual(true, false)
            a.NotEqual(3.14, 2.71)

            // 不同类型(即使值看起来相同)
            a.NotEqual(int32(5), int64(5))
            a.NotEqual(float32(1.0), float64(1.0))
        }

        func TestNotEqualAfterModification(t *testing.T) {
            a := assert.New(t)

            type Counter struct {
                Value int
            }

            counter := Counter{Value: 0}
            before := counter

            // 修改后应该不相等
            counter.Value = 10
            a.NotEqual(before, counter)

            // 验证确实改变了
            a.Equal(0, before.Value)
            a.Equal(10, counter.Value)
        }

        func TestNotEqualSlices(t *testing.T) {
            a := assert.New(t)

            // 不同长度
            a.NotEqual([]int{1, 2, 3}, []int{1, 2})

            // 相同长度不同元素
            a.NotEqual([]int{1, 2, 3}, []int{1, 2, 4})

            // 不同顺序
            a.NotEqual([]int{1, 2, 3}, []int{3, 2, 1})
        }

        func TestNotEqualMaps(t *testing.T) {
            a := assert.New(t)

            m1 := map[string]int{"a": 1, "b": 2}
            m2 := map[string]int{"a": 1, "b": 3}  // b的值不同
            m3 := map[string]int{"a": 1}          // 缺少b

            a.NotEqual(m1, m2)
            a.NotEqual(m1, m3)
        }
        ---

03.Nil和NotNil断言
    a.功能说明
        Nil断言检查值是否为nil,NotNil检查值不为nil。在Go语言中,可以为nil的类型包括指针、接口、切片、map、channel和函数。Nil断言是测试中最常用的存在性检查方法,特别是在检查函数返回值时。
    b.nil类型说明
        Go语言中nil的含义因类型而异。对于指针,nil表示不指向任何对象。对于接口,nil表示没有存储任何值。对于切片和map,nil表示未初始化。对于channel,nil表示未创建。理解nil的语义对正确使用Nil断言很重要。
    c.代码示例
        ---
        // Nil和NotNil断言详解
        package nil

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

        func TestNilPointers(t *testing.T) {
            a := assert.New(t)

            // nil指针
            var ptr *int
            a.Nil(ptr)

            // 非nil指针
            x := 42
            a.NotNil(&x)

            // 结构体指针
            type User struct {
                Name string
            }

            var user *User
            a.Nil(user)

            user = &User{Name: "Alice"}
            a.NotNil(user)
        }

        func TestNilInterfaces(t *testing.T) {
            a := assert.New(t)

            // nil error接口
            var err error
            a.Nil(err)

            // 非nil error
            err = errors.New("something went wrong")
            a.NotNil(err)

            // nil接口值
            var reader io.Reader
            a.Nil(reader)

            // 非nil接口值
            reader = strings.NewReader("hello")
            a.NotNil(reader)
        }

        func TestNilSlices(t *testing.T) {
            a := assert.New(t)

            // nil切片
            var slice []int
            a.Nil(slice)

            // 空切片(不是nil)
            emptySlice := []int{}
            a.NotNil(emptySlice)

            // make创建的切片
            madeSlice := make([]int, 0)
            a.NotNil(madeSlice)

            // nil切片和空切片的区别
            a.Len(slice, 0)       // nil切片长度为0
            a.Len(emptySlice, 0)  // 空切片长度也为0
            // 但它们不相等
            a.NotEqual(slice, emptySlice)
        }

        func TestNilMaps(t *testing.T) {
            a := assert.New(t)

            // nil map
            var m map[string]int
            a.Nil(m)

            // 空map(不是nil)
            emptyMap := map[string]int{}
            a.NotNil(emptyMap)

            // make创建的map
            madeMap := make(map[string]int)
            a.NotNil(madeMap)

            // nil map不能写入(会panic)
            // m["key"] = 1  // panic!

            // 空map可以写入
            emptyMap["key"] = 1
            a.Equal(1, emptyMap["key"])
        }

        func TestNilChannels(t *testing.T) {
            a := assert.New(t)

            // nil channel
            var ch chan int
            a.Nil(ch)

            // 创建的channel
            ch = make(chan int)
            a.NotNil(ch)
            defer close(ch)
        }

        func TestNilFunctions(t *testing.T) {
            a := assert.New(t)

            // nil函数
            var fn func()
            a.Nil(fn)

            // 非nil函数
            fn = func() {}
            a.NotNil(fn)
        }

        func TestNilInReturnValues(t *testing.T) {
            a := assert.New(t)

            // 测试返回nil的函数
            user, err := FindUser(999)
            a.Nil(user)
            a.NotNil(err)

            // 测试返回非nil的函数
            user, err = FindUser(1)
            a.NotNil(user)
            a.Nil(err)
        }

        type User struct {
            ID   int
            Name string
        }

        func FindUser(id int) (*User, error) {
            if id == 999 {
                return nil, errors.New("user not found")
            }
            return &User{ID: id, Name: "Alice"}, nil
        }
        ---

04.True和False断言
    a.功能说明
        True断言检查布尔表达式是否为true,False检查是否为false。虽然看似简单,但在测试复杂条件、验证状态、检查标志位等场景中非常实用。True和False让测试代码更加语义化和易读。
    b.使用技巧
        True和False适合测试返回布尔值的函数,验证复杂的条件表达式,检查对象的状态标志。相比使用Equal(true, value),使用True(value)更加简洁和直观。可以配合自定义错误消息,清楚说明被测试的条件。
    c.代码示例
        ---
        // True和False断言详解
        package boolean

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

        func TestTrueBasics(t *testing.T) {
            a := assert.New(t)

            // 直接的布尔值
            a.True(true)
            a.False(false)

            // 布尔表达式
            a.True(5 > 3)
            a.False(5 < 3)
            a.True(10 == 10)
            a.False(10 != 10)
        }

        func TestTrueFunctions(t *testing.T) {
            a := assert.New(t)

            // 测试返回bool的函数
            a.True(IsValidEmail("[email protected]"))
            a.False(IsValidEmail("invalid"))

            a.True(IsPrime(7))
            a.False(IsPrime(8))

            a.True(IsEmpty(""))
            a.False(IsEmpty("hello"))
        }

        func TestTrueConditions(t *testing.T) {
            a := assert.New(t)

            user := User{
                Name:     "Alice",
                Age:      25,
                Active:   true,
                Verified: true,
            }

            // 测试对象状态
            a.True(user.Active, "用户应该是激活状态")
            a.True(user.Verified, "用户应该已验证")
            a.True(user.Age >= 18, "用户应该是成年人")
            a.True(len(user.Name) > 0, "用户名不应为空")

            // 组合条件
            a.True(user.Active && user.Verified, "用户应该激活且已验证")
            a.True(user.Age >= 18 && user.Age < 65, "用户年龄应在18-65之间")
        }

        func TestTrueStringOperations(t *testing.T) {
            a := assert.New(t)

            text := "Hello, World!"

            // 字符串检查
            a.True(strings.Contains(text, "World"))
            a.True(strings.HasPrefix(text, "Hello"))
            a.True(strings.HasSuffix(text, "!"))
            a.True(len(text) > 10)

            // 正则匹配
            matched, _ := regexp.MatchString(`^Hello`, text)
            a.True(matched)
        }

        func TestTrueCollections(t *testing.T) {
            a := assert.New(t)

            numbers := []int{1, 2, 3, 4, 5}

            // 集合检查
            a.True(len(numbers) == 5)
            a.True(len(numbers) > 0)
            a.True(contains(numbers, 3))
            a.False(contains(numbers, 10))

            // map检查
            config := map[string]string{
                "host": "localhost",
                "port": "8080",
            }

            _, exists := config["host"]
            a.True(exists, "配置应该包含host")

            _, exists = config["password"]
            a.False(exists, "配置不应该包含password")
        }

        func TestTrueCustomValidation(t *testing.T) {
            a := assert.New(t)

            // 自定义验证逻辑
            password := "MyP@ssw0rd"
            a.True(len(password) >= 8, "密码长度应该>=8")
            a.True(containsUpperCase(password), "密码应包含大写字母")
            a.True(containsLowerCase(password), "密码应包含小写字母")
            a.True(containsDigit(password), "密码应包含数字")
            a.True(containsSpecialChar(password), "密码应包含特殊字符")
        }

        type User struct {
            Name     string
            Age      int
            Active   bool
            Verified bool
        }

        func IsValidEmail(email string) bool {
            return strings.Contains(email, "@") && strings.Contains(email, ".")
        }

        func IsPrime(n int) bool {
            if n < 2 {
                return false
            }
            for i := 2; i*i <= n; i++ {
                if n%i == 0 {
                    return false
                }
            }
            return true
        }

        func IsEmpty(s string) bool {
            return len(s) == 0
        }

        func contains(slice []int, value int) bool {
            for _, v := range slice {
                if v == value {
                    return true
                }
            }
            return false
        }

        func containsUpperCase(s string) bool {
            for _, c := range s {
                if c >= 'A' && c <= 'Z' {
                    return true
                }
            }
            return false
        }

        func containsLowerCase(s string) bool {
            for _, c := range s {
                if c >= 'a' && c <= 'z' {
                    return true
                }
            }
            return false
        }

        func containsDigit(s string) bool {
            for _, c := range s {
                if c >= '0' && c <= '9' {
                    return true
                }
            }
            return false
        }

        func containsSpecialChar(s string) bool {
            return strings.ContainsAny(s, "!@#$%^&*()_+-=[]{}|;:,.<>?")
        }
        ---

3.2 比较断言

01.数值比较断言
    a.Greater和Less系列
        testify提供完整的数值比较断言方法。Greater检查值大于,GreaterOrEqual检查大于等于,Less检查小于,LessOrEqual检查小于等于。这些方法支持所有可比较的数值类型,让数值范围验证变得简单直观。
    b.Positive和Negative
        Positive断言检查数值是否为正数,Negative检查是否为负数。这两个方法是Greater和Less的特殊形式,用于检查数值的符号,使测试代码更加语义化。
    c.代码示例
        ---
        // 数值比较断言详解
        package comparison

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

        func TestGreaterAndLess(t *testing.T) {
            a := assert.New(t)

            // Greater:大于
            a.Greater(10, 5)
            a.Greater(3.14, 2.71)
            a.Greater(int64(1000), int64(999))

            // GreaterOrEqual:大于等于
            a.GreaterOrEqual(10, 10)
            a.GreaterOrEqual(10, 5)
            a.GreaterOrEqual(100.0, 100.0)

            // Less:小于
            a.Less(5, 10)
            a.Less(-1, 0)
            a.Less(1.5, 2.5)

            // LessOrEqual:小于等于
            a.LessOrEqual(5, 5)
            a.LessOrEqual(5, 10)
            a.LessOrEqual(-10.0, -10.0)
        }

        func TestPositiveAndNegative(t *testing.T) {
            a := assert.New(t)

            // Positive:正数
            a.Positive(1)
            a.Positive(42)
            a.Positive(3.14)
            a.Positive(int64(1000))

            // Negative:负数
            a.Negative(-1)
            a.Negative(-42)
            a.Negative(-3.14)
            a.Negative(int64(-1000))

            // 零不是正数也不是负数
            // a.Positive(0)  // 失败
            // a.Negative(0)  // 失败
        }

        func TestComparisonUseCases(t *testing.T) {
            a := assert.New(t)

            // 场景1:年龄验证
            age := 25
            a.GreaterOrEqual(age, 18, "必须是成年人")
            a.Less(age, 65, "必须是工作年龄")

            // 场景2:分数验证
            score := 85.5
            a.GreaterOrEqual(score, 0.0, "分数不能为负")
            a.LessOrEqual(score, 100.0, "分数不能超过100")
            a.GreaterOrEqual(score, 60.0, "分数及格")

            // 场景3:价格验证
            price := 99.99
            a.Positive(price, "价格必须为正")
            a.Less(price, 1000.0, "价格合理")

            // 场景4:余额验证
            balance := -50.0
            a.Negative(balance, "账户透支")

            // 场景5:数组长度验证
            items := []int{1, 2, 3, 4, 5}
            a.Greater(len(items), 0, "列表不能为空")
            a.LessOrEqual(len(items), 100, "列表不能太长")
        }

        func TestComparisonWithDifferentTypes(t *testing.T) {
            a := assert.New(t)

            // int类型
            var i int = 10
            a.Greater(i, 5)

            // int32类型
            var i32 int32 = 10
            a.Greater(i32, int32(5))

            // int64类型
            var i64 int64 = 10
            a.Greater(i64, int64(5))

            // float32类型
            var f32 float32 = 10.5
            a.Greater(f32, float32(5.5))

            // float64类型
            var f64 float64 = 10.5
            a.Greater(f64, 5.5)

            // uint类型
            var u uint = 10
            a.Greater(u, uint(5))
        }

        func TestComparisonWithTime(t *testing.T) {
            a := assert.New(t)

            now := time.Now()
            past := now.Add(-1 * time.Hour)
            future := now.Add(1 * time.Hour)

            // 时间比较(Time实现了comparable接口)
            a.True(future.After(now))
            a.True(past.Before(now))

            // 使用Greater比较Unix时间戳
            a.Greater(future.Unix(), now.Unix())
            a.Less(past.Unix(), now.Unix())
        }
        ---

02.浮点数比较断言
    a.InDelta系列
        由于浮点数精度问题,直接使用Equal比较浮点数可能失败。InDelta允许指定误差范围,只要两个浮点数的差值在delta范围内就认为相等。InDeltaSlice和InDeltaMapValues分别用于比较浮点数切片和map。
    b.InEpsilon相对误差
        InEpsilon使用相对误差而非绝对误差进行比较,适合比较不同数量级的浮点数。epsilon表示相对误差的百分比,如0.01表示允许1%的误差。
    c.代码示例
        ---
        // 浮点数比较断言详解
        package float

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

        func TestInDelta(t *testing.T) {
            a := assert.New(t)

            // InDelta:绝对误差比较
            // 0.1 + 0.2 在浮点数运算中不完全等于0.3
            result := 0.1 + 0.2
            a.InDelta(0.3, result, 0.0001, "允许0.0001的误差")

            // 直接Equal会失败
            // a.Equal(0.3, result)  // 可能失败

            // 更多示例
            a.InDelta(3.14159, math.Pi, 0.00001)
            a.InDelta(2.71828, math.E, 0.00001)

            // 较大的数值
            a.InDelta(1000000.0, 1000000.5, 1.0)
        }

        func TestInDeltaSlice(t *testing.T) {
            a := assert.New(t)

            // InDeltaSlice:切片中每个元素都允许误差
            expected := []float64{1.0, 2.0, 3.0}
            actual := []float64{1.0001, 1.9999, 3.0001}

            a.InDeltaSlice(expected, actual, 0.001)

            // 科学计算结果比较
            calculated := []float64{
                math.Sqrt(2),
                math.Sqrt(3),
                math.Sqrt(5),
            }

            expected2 := []float64{
                1.414213562373095,
                1.732050807568877,
                2.236067977499790,
            }

            a.InDeltaSlice(expected2, calculated, 0.000000000000001)
        }

        func TestInDeltaMapValues(t *testing.T) {
            a := assert.New(t)

            // InDeltaMapValues:map中每个值都允许误差
            expected := map[string]float64{
                "pi": 3.14159,
                "e":  2.71828,
            }

            actual := map[string]float64{
                "pi": 3.14160,
                "e":  2.71829,
            }

            a.InDeltaMapValues(expected, actual, 0.00001)
        }

        func TestInEpsilon(t *testing.T) {
            a := assert.New(t)

            // InEpsilon:相对误差比较
            // 允许1%的相对误差
            a.InEpsilon(1000, 1010, 0.01)
            a.InEpsilon(100, 101, 0.01)

            // 不同数量级的数值
            a.InEpsilon(1000000, 1010000, 0.01)  // 1%误差
            a.InEpsilon(0.001, 0.00101, 0.01)    // 1%误差

            // 金融计算
            price := 99.99
            discountedPrice := price * 0.9
            a.InEpsilon(89.991, discountedPrice, 0.001)
        }

        func TestInEpsilonSlice(t *testing.T) {
            a := assert.New(t)

            // InEpsilonSlice:切片相对误差比较
            expected := []float64{100, 200, 300}
            actual := []float64{101, 202, 303}

            a.InEpsilonSlice(expected, actual, 0.02)  // 允许2%误差
        }

        func TestFloatComparisonScenarios(t *testing.T) {
            a := assert.New(t)

            // 场景1:物理计算
            g := 9.8          // 重力加速度
            calculated := 9.81
            a.InDelta(g, calculated, 0.1)

            // 场景2:统计计算
            mean := 10.5
            variance := 2.3
            stddev := math.Sqrt(variance)
            expectedStddev := 1.5165750888103102
            a.InDelta(expectedStddev, stddev, 0.0001)

            // 场景3:金融计算
            principal := 10000.0
            rate := 0.05
            years := 10.0
            amount := principal * math.Pow(1+rate, years)
            expectedAmount := 16288.95
            a.InDelta(expectedAmount, amount, 1.0)

            // 场景4:机器学习(损失函数)
            loss := 0.0234567
            targetLoss := 0.0234000
            a.InDelta(targetLoss, loss, 0.0001)
        }

        func TestFloatEdgeCases(t *testing.T) {
            a := assert.New(t)

            // 零值比较
            a.InDelta(0.0, 0.0, 0.0001)
            a.InDelta(0.0, 0.00001, 0.0001)

            // 负数比较
            a.InDelta(-3.14, -3.141, 0.01)

            // 非常小的数
            a.InDelta(1e-10, 1.1e-10, 1e-11)

            // 非常大的数
            a.InDelta(1e10, 1.0000001e10, 1e3)

            // 无穷大
            a.Equal(math.Inf(1), math.Inf(1))
            a.NotEqual(math.Inf(1), math.Inf(-1))

            // NaN(不能用Equal比较)
            a.True(math.IsNaN(math.NaN()))
        }
        ---

03.字符串比较断言
    a.Contains和NotContains
        Contains检查字符串是否包含子串,NotContains检查不包含。这两个方法也适用于切片和map,用于检查集合是否包含特定元素。Contains是测试中非常常用的断言方法。
    b.Regexp和NotRegexp
        Regexp使用正则表达式匹配字符串,NotRegexp检查不匹配。适合验证字符串格式如邮箱、电话号码、URL等。正则表达式可以是字符串或编译好的regexp.Regexp对象。
    c.代码示例
        ---
        // 字符串比较断言详解
        package string

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

        func TestContains(t *testing.T) {
            a := assert.New(t)

            // 字符串包含
            text := "Hello, World!"
            a.Contains(text, "World")
            a.Contains(text, "Hello")
            a.Contains(text, ",")
            a.NotContains(text, "Goodbye")

            // 大小写敏感
            a.Contains("Hello", "Hell")
            a.NotContains("Hello", "hell")  // 小写

            // 空字符串
            a.Contains("hello", "")  // 任何字符串都包含空字符串

            // 错误消息中的关键字
            errMsg := "failed to connect to database: connection timeout"
            a.Contains(errMsg, "database")
            a.Contains(errMsg, "timeout")
            a.NotContains(errMsg, "success")
        }

        func TestRegexp(t *testing.T) {
            a := assert.New(t)

            // 邮箱验证
            email := "[email protected]"
            emailPattern := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`
            a.Regexp(emailPattern, email)

            invalidEmail := "invalid-email"
            a.NotRegexp(emailPattern, invalidEmail)

            // 电话号码验证
            phone := "13812345678"
            phonePattern := `^1[3-9]\d{9}$`
            a.Regexp(phonePattern, phone)

            // URL验证
            url := "https://www.example.com/path?query=value"
            urlPattern := `^https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}`
            a.Regexp(urlPattern, url)

            // 日期格式验证
            date := "2024-01-15"
            datePattern := `^\d{4}-\d{2}-\d{2}$`
            a.Regexp(datePattern, date)

            // IP地址验证
            ip := "192.168.1.1"
            ipPattern := `^(\d{1,3}\.){3}\d{1,3}$`
            a.Regexp(ipPattern, ip)
        }

        func TestRegexpWithCompiledPattern(t *testing.T) {
            a := assert.New(t)

            // 预编译的正则表达式
            emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)

            a.Regexp(emailRegex, "[email protected]")
            a.NotRegexp(emailRegex, "invalid")
        }

        func TestStringComparisonScenarios(t *testing.T) {
            a := assert.New(t)

            // 场景1:日志消息验证
            logMsg := "[INFO] 2024-01-15 10:30:45 User logged in successfully"
            a.Contains(logMsg, "[INFO]")
            a.Contains(logMsg, "User logged in")
            a.Regexp(`\d{4}-\d{2}-\d{2}`, logMsg)

            // 场景2:API响应验证
            apiResponse := `{"status":"success","data":{"id":123,"name":"Alice"}}`
            a.Contains(apiResponse, "success")
            a.Contains(apiResponse, "Alice")
            a.Regexp(`"id":\d+`, apiResponse)

            // 场景3:文件路径验证
            filepath := "/home/user/documents/file.txt"
            a.Contains(filepath, "/documents/")
            a.Contains(filepath, "file.txt")
            a.Regexp(`\.txt$`, filepath)

            // 场景4:版本号验证
            version := "v1.2.3"
            a.Regexp(`^v\d+\.\d+\.\d+$`, version)

            // 场景5:SQL查询验证
            query := "SELECT * FROM users WHERE age > 18 ORDER BY name"
            a.Contains(query, "SELECT")
            a.Contains(query, "FROM users")
            a.Regexp(`WHERE\s+\w+\s*[><=]`, query)
        }

        func TestHasPrefixAndSuffix(t *testing.T) {
            a := assert.New(t)

            text := "prefix_content_suffix"

            // 使用Contains模拟HasPrefix
            a.True(strings.HasPrefix(text, "prefix_"))
            a.False(strings.HasPrefix(text, "suffix"))

            // 使用Contains模拟HasSuffix
            a.True(strings.HasSuffix(text, "_suffix"))
            a.False(strings.HasSuffix(text, "prefix"))

            // 或使用Regexp
            a.Regexp(`^prefix_`, text)
            a.Regexp(`_suffix$`, text)
        }
        ---

04.Same断言
    a.功能说明
        Same断言检查两个指针是否指向同一个对象,即内存地址相同。与Equal不同,Same不比较值,只比较指针地址。Same适合验证对象引用、单例模式、缓存命中等场景。
    b.Same vs Equal
        Equal比较值是否相等,即使是不同对象只要值相同就通过。Same比较指针地址,只有指向同一对象才通过。理解两者的区别对正确使用断言很重要。
    c.代码示例
        ---
        // Same断言详解
        package same

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

        func TestSameBasics(t *testing.T) {
            a := assert.New(t)

            // 指向同一对象
            obj := &User{Name: "Alice"}
            ref1 := obj
            ref2 := obj

            a.Same(ref1, ref2)  // 通过:指向同一对象
            a.Equal(ref1, ref2) // 也通过:值相等

            // 不同对象但值相等
            obj1 := &User{Name: "Alice"}
            obj2 := &User{Name: "Alice"}

            a.NotSame(obj1, obj2)  // 通过:不是同一对象
            a.Equal(obj1, obj2)    // 通过:值相等
        }

        func TestSameSingleton(t *testing.T) {
            a := assert.New(t)

            // 测试单例模式
            config1 := GetConfig()
            config2 := GetConfig()

            a.Same(config1, config2, "配置应该是单例")
        }

        func TestSameCache(t *testing.T) {
            a := assert.New(t)

            cache := NewCache()

            // 第一次获取,创建对象
            user1 := cache.Get("user1")
            a.NotNil(user1)

            // 第二次获取,应该返回缓存的对象
            user2 := cache.Get("user1")
            a.Same(user1, user2, "应该返回缓存的对象")

            // 不同的key,返回不同的对象
            user3 := cache.Get("user2")
            a.NotSame(user1, user3)
        }

        func TestSameSlices(t *testing.T) {
            a := assert.New(t)

            slice1 := []int{1, 2, 3}
            slice2 := slice1  // 共享底层数组
            slice3 := []int{1, 2, 3}  // 新切片

            // slice1和slice2共享底层数组,但切片头不同
            // Same检查切片头的地址
            a.Equal(slice1, slice2)
            // a.Same(&slice1, &slice2)  // 需要比较切片头的地址

            // slice1和slice3值相等但是不同的切片
            a.Equal(slice1, slice3)
            a.NotSame(&slice1, &slice3)
        }

        type User struct {
            Name string
        }

        var configInstance *Config

        type Config struct {
            Host string
        }

        func GetConfig() *Config {
            if configInstance == nil {
                configInstance = &Config{Host: "localhost"}
            }
            return configInstance
        }

        type Cache struct {
            data map[string]*User
        }

        func NewCache() *Cache {
            return &Cache{data: make(map[string]*User)}
        }

        func (c *Cache) Get(key string) *User {
            if user, exists := c.data[key]; exists {
                return user
            }
            user := &User{Name: key}
            c.data[key] = user
            return user
        }
        ---

3.3 集合断言

01.Len断言
    a.功能说明
        Len断言检查集合的长度,支持字符串、数组、切片、map、channel等所有具有长度概念的类型。Len是最常用的集合断言之一,用于验证集合大小符合预期。相比手动检查len()然后用Equal断言,Len提供更清晰的错误信息。
    b.使用场景
        Len适用于验证查询结果数量,检查批处理的元素个数,确认过滤后的集合大小,验证分页数据的条数。在测试数据操作时,Len是必不可少的断言方法。
    c.代码示例
        ---
        // Len断言详解
        package length

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

        func TestLenBasics(t *testing.T) {
            a := assert.New(t)

            // 字符串长度
            a.Len("hello", 5)
            a.Len("", 0)
            a.Len("你好世界", 4)  // UTF-8字符数

            // 切片长度
            a.Len([]int{1, 2, 3}, 3)
            a.Len([]int{}, 0)
            a.Len([]string{"a", "b", "c", "d"}, 4)

            // 数组长度
            arr := [5]int{1, 2, 3, 4, 5}
            a.Len(arr, 5)

            // map长度
            m := map[string]int{"a": 1, "b": 2}
            a.Len(m, 2)

            // channel长度(缓冲区中的元素数)
            ch := make(chan int, 10)
            a.Len(ch, 0)
            ch <- 1
            ch <- 2
            a.Len(ch, 2)
        }

        func TestLenScenarios(t *testing.T) {
            a := assert.New(t)

            // 场景1:查询结果验证
            users := QueryUsers()
            a.Len(users, 3, "应该返回3个用户")

            // 场景2:过滤操作验证
            numbers := []int{1, 2, 3, 4, 5, 6}
            evens := Filter(numbers, func(n int) bool { return n%2 == 0 })
            a.Len(evens, 3, "应该有3个偶数")

            // 场景3:分页数据验证
            pageSize := 10
            items := GetPage(1, pageSize)
            a.Len(items, pageSize, "每页应该有10条数据")

            // 场景4:批处理验证
            batch := []string{"item1", "item2", "item3"}
            results := ProcessBatch(batch)
            a.Len(results, len(batch), "结果数量应该等于输入数量")
        }

        func TestLenEmpty(t *testing.T) {
            a := assert.New(t)

            // 空集合
            a.Len([]int{}, 0)
            a.Len("", 0)
            a.Len(map[string]int{}, 0)

            // 也可以使用Empty断言
            a.Empty([]int{})
            a.Empty("")
            a.Empty(map[string]int{})
        }

        func TestLenAfterOperations(t *testing.T) {
            a := assert.New(t)

            // 添加操作
            list := []int{1, 2, 3}
            a.Len(list, 3)

            list = append(list, 4, 5)
            a.Len(list, 5)

            // 删除操作
            list = list[:3]
            a.Len(list, 3)

            // map操作
            m := make(map[string]int)
            a.Len(m, 0)

            m["a"] = 1
            m["b"] = 2
            a.Len(m, 2)

            delete(m, "a")
            a.Len(m, 1)
        }

        // 辅助函数
        type User struct{ Name string }

        func QueryUsers() []User {
            return []User{{Name: "Alice"}, {Name: "Bob"}, {Name: "Charlie"}}
        }

        func Filter(nums []int, fn func(int) bool) []int {
            result := []int{}
            for _, n := range nums {
                if fn(n) {
                    result = append(result, n)
                }
            }
            return result
        }

        func GetPage(page, size int) []string {
            items := make([]string, size)
            for i := 0; i < size; i++ {
                items[i] = fmt.Sprintf("item%d", i)
            }
            return items
        }

        func ProcessBatch(items []string) []string {
            return items
        }
        ---

02.Empty和NotEmpty断言
    a.功能说明
        Empty断言检查集合是否为空,NotEmpty检查不为空。Empty不仅检查长度为0,还检查零值。对于字符串、切片、数组、map,Empty检查长度为0。对于指针、接口,Empty检查是否为nil。对于数值,Empty检查是否为0。
    b.Empty vs Len
        Empty和Len(x, 0)效果相似但语义不同。Empty表达"这个集合是空的",Len(x, 0)表达"这个集合的长度是0"。Empty还能处理nil切片和空切片,而Len只能处理非nil值。使用Empty让测试意图更清晰。
    c.代码示例
        ---
        // Empty和NotEmpty断言详解
        package empty

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

        func TestEmpty(t *testing.T) {
            a := assert.New(t)

            // 空字符串
            a.Empty("")
            a.NotEmpty("hello")

            // 空切片
            a.Empty([]int{})
            var nilSlice []int
            a.Empty(nilSlice)  // nil切片也是空的
            a.NotEmpty([]int{1, 2, 3})

            // 空map
            a.Empty(map[string]int{})
            var nilMap map[string]int
            a.Empty(nilMap)  // nil map也是空的
            a.NotEmpty(map[string]int{"a": 1})

            // 零值
            a.Empty(0)
            a.Empty(false)
            a.Empty(0.0)
            a.NotEmpty(1)
            a.NotEmpty(true)
        }

        func TestEmptyScenarios(t *testing.T) {
            a := assert.New(t)

            // 场景1:搜索结果为空
            results := Search("nonexistent")
            a.Empty(results, "应该没有搜索结果")

            // 场景2:过滤后为空
            numbers := []int{1, 3, 5, 7}
            evens := FilterEvens(numbers)
            a.Empty(evens, "没有偶数")

            // 场景3:清空操作
            list := []string{"a", "b", "c"}
            a.NotEmpty(list)

            list = []string{}
            a.Empty(list, "列表应该被清空")

            // 场景4:可选字段为空
            config := Config{
                Host: "localhost",
                // Port为零值
            }
            a.NotEmpty(config.Host)
            a.Empty(config.Port, "Port未设置")
        }

        func TestEmptyVsNil(t *testing.T) {
            a := assert.New(t)

            // nil切片
            var nilSlice []int
            a.Nil(nilSlice)
            a.Empty(nilSlice)

            // 空切片(不是nil)
            emptySlice := []int{}
            a.NotNil(emptySlice)
            a.Empty(emptySlice)

            // Empty可以处理两种情况
            // 但Nil只能检查nil
        }

        type Config struct {
            Host string
            Port int
        }

        func Search(keyword string) []string {
            return []string{}
        }

        func FilterEvens(nums []int) []int {
            result := []int{}
            for _, n := range nums {
                if n%2 == 0 {
                    result = append(result, n)
                }
            }
            return result
        }
        ---

03.Contains断言
    a.功能说明
        Contains断言检查集合是否包含指定元素。对于字符串,检查是否包含子串。对于切片和数组,检查是否包含指定元素。对于map,检查是否包含指定的键。Contains是实现集合成员检查的便捷方法。
    b.NotContains
        NotContains检查集合不包含指定元素,是Contains的反向断言。适用于验证黑名单、排除特定项、确认删除操作等场景。
    c.代码示例
        ---
        // Contains断言详解
        package contains

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

        func TestContainsString(t *testing.T) {
            a := assert.New(t)

            text := "Hello, World!"
            a.Contains(text, "World")
            a.Contains(text, "Hello")
            a.Contains(text, ", ")
            a.NotContains(text, "Goodbye")

            // 大小写敏感
            a.Contains("Hello", "Hell")
            a.NotContains("Hello", "hell")
        }

        func TestContainsSlice(t *testing.T) {
            a := assert.New(t)

            numbers := []int{1, 2, 3, 4, 5}
            a.Contains(numbers, 3)
            a.Contains(numbers, 1)
            a.Contains(numbers, 5)
            a.NotContains(numbers, 10)

            // 字符串切片
            names := []string{"Alice", "Bob", "Charlie"}
            a.Contains(names, "Bob")
            a.NotContains(names, "David")
        }

        func TestContainsMap(t *testing.T) {
            a := assert.New(t)

            // map包含检查键
            m := map[string]int{
                "a": 1,
                "b": 2,
                "c": 3,
            }

            a.Contains(m, "a")
            a.Contains(m, "b")
            a.NotContains(m, "d")
        }

        func TestContainsScenarios(t *testing.T) {
            a := assert.New(t)

            // 场景1:权限检查
            userPermissions := []string{"read", "write", "delete"}
            a.Contains(userPermissions, "write", "用户应该有写权限")
            a.NotContains(userPermissions, "admin", "用户不应该有管理员权限")

            // 场景2:标签检查
            tags := []string{"go", "testing", "unit-test"}
            a.Contains(tags, "testing")

            // 场景3:错误消息检查
            errMsg := "failed to connect to database"
            a.Contains(errMsg, "database")
            a.Contains(errMsg, "failed")

            // 场景4:配置键检查
            config := map[string]string{
                "host": "localhost",
                "port": "8080",
            }
            a.Contains(config, "host")
            a.NotContains(config, "password")
        }

        func TestContainsStructSlice(t *testing.T) {
            a := assert.New(t)

            type User struct {
                ID   int
                Name string
            }

            users := []User{
                {ID: 1, Name: "Alice"},
                {ID: 2, Name: "Bob"},
                {ID: 3, Name: "Charlie"},
            }

            // 直接比较结构体
            a.Contains(users, User{ID: 2, Name: "Bob"})
            a.NotContains(users, User{ID: 4, Name: "David"})
        }
        ---

04.ElementsMatch断言
    a.功能说明
        ElementsMatch断言检查两个切片包含相同的元素,但忽略顺序。这对于测试返回无序结果的函数非常有用,如查询数据库、并发处理、集合操作等。ElementsMatch还能正确处理重复元素。
    b.ElementsMatch vs Equal
        Equal要求元素顺序完全一致,ElementsMatch忽略顺序只检查元素集合。当函数返回的顺序不确定时,使用ElementsMatch避免测试不稳定。如果顺序很重要,使用Equal。
    c.代码示例
        ---
        // ElementsMatch断言详解
        package elementsmatch

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

        func TestElementsMatchBasics(t *testing.T) {
            a := assert.New(t)

            // 相同元素,不同顺序
            a.ElementsMatch(
                []int{1, 2, 3},
                []int{3, 2, 1},
            )

            // 相同元素,顺序相同
            a.ElementsMatch(
                []int{1, 2, 3},
                []int{1, 2, 3},
            )

            // 包含重复元素
            a.ElementsMatch(
                []int{1, 2, 2, 3},
                []int{2, 3, 1, 2},
            )

            // 不同元素
            // a.ElementsMatch([]int{1, 2, 3}, []int{1, 2, 4})  // 失败
        }

        func TestElementsMatchStrings(t *testing.T) {
            a := assert.New(t)

            expected := []string{"apple", "banana", "cherry"}
            actual := []string{"cherry", "apple", "banana"}

            a.ElementsMatch(expected, actual)
        }

        func TestElementsMatchScenarios(t *testing.T) {
            a := assert.New(t)

            // 场景1:数据库查询(结果顺序不确定)
            expected := []User{
                {ID: 1, Name: "Alice"},
                {ID: 2, Name: "Bob"},
                {ID: 3, Name: "Charlie"},
            }

            actual := QueryUsers()  // 返回顺序可能不同
            a.ElementsMatch(expected, actual)

            // 场景2:并发处理结果
            expected2 := []int{1, 2, 3, 4, 5}
            actual2 := ProcessConcurrently([]int{1, 2, 3, 4, 5})
            a.ElementsMatch(expected2, actual2)

            // 场景3:集合操作
            set1 := []string{"a", "b", "c"}
            set2 := []string{"c", "a", "b"}
            a.ElementsMatch(set1, set2)
        }

        func TestElementsMatchWithDuplicates(t *testing.T) {
            a := assert.New(t)

            // 重复元素的数量必须相同
            a.ElementsMatch(
                []int{1, 1, 2, 2, 3},
                []int{2, 1, 3, 2, 1},
            )

            // 重复次数不同会失败
            // a.ElementsMatch(
            //     []int{1, 1, 2},
            //     []int{1, 2, 2},
            // )  // 失败
        }

        type User struct {
            ID   int
            Name string
        }

        func QueryUsers() []User {
            // 模拟数据库查询,顺序不确定
            return []User{
                {ID: 2, Name: "Bob"},
                {ID: 1, Name: "Alice"},
                {ID: 3, Name: "Charlie"},
            }
        }

        func ProcessConcurrently(items []int) []int {
            // 模拟并发处理,返回顺序不确定
            return []int{5, 3, 1, 4, 2}
        }
        ---

05.Subset和NotSubset断言
    a.功能说明
        Subset断言检查一个集合是否是另一个集合的子集,即第一个集合的所有元素都在第二个集合中。NotSubset检查不是子集关系。子集断言适合验证过滤操作、权限检查、配置验证等场景。
    b.子集关系
        空集合是任何集合的子集。集合是自身的子集。子集关系不关心顺序,只关心元素是否都存在。子集可以有重复元素,但重复次数不能超过父集合。
    c.代码示例
        ---
        // Subset断言详解
        package subset

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

        func TestSubsetBasics(t *testing.T) {
            a := assert.New(t)

            all := []int{1, 2, 3, 4, 5}

            // 子集
            a.Subset(all, []int{2, 3})
            a.Subset(all, []int{1, 5})
            a.Subset(all, []int{1, 2, 3, 4, 5})  // 自身是子集
            a.Subset(all, []int{})                // 空集是子集

            // 不是子集
            a.NotSubset(all, []int{1, 6})  // 6不在all中
            a.NotSubset(all, []int{10})
        }

        func TestSubsetScenarios(t *testing.T) {
            a := assert.New(t)

            // 场景1:权限验证
            allPermissions := []string{"read", "write", "delete", "admin"}
            userPermissions := []string{"read", "write"}
            a.Subset(allPermissions, userPermissions, "用户权限应该是有效权限的子集")

            // 场景2:过滤操作验证
            allItems := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
            filtered := FilterGreaterThan(allItems, 5)
            a.Subset(allItems, filtered, "过滤结果应该是原集合的子集")

            // 场景3:配置项验证
            validOptions := []string{"debug", "info", "warn", "error"}
            userOptions := []string{"info", "error"}
            a.Subset(validOptions, userOptions, "用户选项应该是有效选项的子集")
        }

        func TestSubsetStrings(t *testing.T) {
            a := assert.New(t)

            allTags := []string{"go", "python", "java", "javascript", "rust"}
            projectTags := []string{"go", "rust"}

            a.Subset(allTags, projectTags)
        }

        func FilterGreaterThan(nums []int, threshold int) []int {
            result := []int{}
            for _, n := range nums {
                if n > threshold {
                    result = append(result, n)
                }
            }
            return result
        }
        ---

3.4 类型断言

01.IsType断言
    a.功能说明
        IsType断言检查值的类型是否与指定类型相同。使用reflect包进行类型检查,支持所有Go类型包括基本类型、结构体、接口、指针等。IsType在测试接口实现、类型转换、反序列化等场景中非常有用。
    b.类型匹配规则
        IsType进行精确的类型匹配,不进行类型转换。int32和int64被认为是不同类型。*User和User被认为是不同类型。interface{}可以匹配任何类型,但IsType检查的是实际存储的类型。
    c.代码示例
        ---
        // IsType断言详解
        package istype

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

        func TestIsTypeBasics(t *testing.T) {
            a := assert.New(t)

            // 基本类型
            a.IsType(0, 42)                    // int
            a.IsType("", "hello")              // string
            a.IsType(false, true)              // bool
            a.IsType(0.0, 3.14)                // float64
            a.IsType(int32(0), int32(42))      // int32
            a.IsType(int64(0), int64(100))     // int64

            // 不同的整数类型不匹配
            // a.IsType(int32(0), 42)          // 失败:int32 != int
            // a.IsType(int64(0), 42)          // 失败:int64 != int
        }

        func TestIsTypeStructs(t *testing.T) {
            a := assert.New(t)

            type User struct {
                Name string
                Age  int
            }

            user := User{Name: "Alice", Age: 25}

            // 结构体类型
            a.IsType(User{}, user)

            // 结构体指针类型
            a.IsType(&User{}, &user)

            // 结构体和指针是不同类型
            // a.IsType(User{}, &user)         // 失败
        }

        func TestIsTypeSlicesAndMaps(t *testing.T) {
            a := assert.New(t)

            // 切片类型
            a.IsType([]int{}, []int{1, 2, 3})
            a.IsType([]string{}, []string{"a", "b"})

            // map类型
            a.IsType(map[string]int{}, map[string]int{"a": 1})

            // 不同的切片类型不匹配
            // a.IsType([]int{}, []string{})   // 失败
        }

        func TestIsTypeInterfaces(t *testing.T) {
            a := assert.New(t)

            type Reader interface {
                Read(p []byte) (n int, err error)
            }

            var r Reader
            var w io.Writer

            // interface{}类型
            var any interface{} = 42
            a.IsType(0, any)  // 检查实际存储的int类型

            any = "hello"
            a.IsType("", any)  // 检查实际存储的string类型
        }

        func TestIsTypeScenarios(t *testing.T) {
            a := assert.New(t)

            // 场景1:JSON反序列化验证
            var result interface{}
            json.Unmarshal([]byte(`{"name":"Alice"}`), &result)
            a.IsType(map[string]interface{}{}, result)

            // 场景2:工厂函数返回类型
            obj := CreateObject("user")
            a.IsType(&User{}, obj)

            // 场景3:类型转换验证
            var data interface{} = []int{1, 2, 3}
            a.IsType([]int{}, data)

            // 可以安全地进行类型断言
            if slice, ok := data.([]int); ok {
                a.Len(slice, 3)
            }
        }

        type User struct {
            Name string
            Age  int
        }

        func CreateObject(objType string) interface{} {
            switch objType {
            case "user":
                return &User{}
            default:
                return nil
            }
        }
        ---

02.Implements断言
    a.功能说明
        Implements断言检查类型是否实现了指定的接口。这对于验证接口实现、测试多态性、确保API契约等场景非常重要。Implements可以在编译期无法检查接口实现的动态场景中使用。
    b.接口检查语法
        Implements的第一个参数必须是接口类型的nil指针,使用(*InterfaceName)(nil)语法。第二个参数是要检查的值或指针。如果类型实现了接口的所有方法,断言通过。
    c.代码示例
        ---
        // Implements断言详解
        package implements

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

        func TestImplementsBasics(t *testing.T) {
            a := assert.New(t)

            // bytes.Buffer实现了io.Writer接口
            var buf bytes.Buffer
            a.Implements((*io.Writer)(nil), &buf)

            // bytes.Buffer实现了io.Reader接口
            a.Implements((*io.Reader)(nil), &buf)

            // strings.Reader实现了io.Reader接口
            reader := strings.NewReader("hello")
            a.Implements((*io.Reader)(nil), reader)
        }

        func TestImplementsCustomInterface(t *testing.T) {
            a := assert.New(t)

            // 定义接口
            type Validator interface {
                Validate() error
            }

            type Saver interface {
                Save() error
            }

            // 定义实现
            type User struct {
                Name string
            }

            func (u *User) Validate() error {
                if u.Name == "" {
                    return errors.New("name is required")
                }
                return nil
            }

            func (u *User) Save() error {
                // 保存逻辑
                return nil
            }

            user := &User{Name: "Alice"}

            // 验证接口实现
            a.Implements((*Validator)(nil), user)
            a.Implements((*Saver)(nil), user)
        }

        func TestImplementsScenarios(t *testing.T) {
            a := assert.New(t)

            // 场景1:插件系统验证
            plugin := LoadPlugin("user-plugin")
            a.Implements((*Plugin)(nil), plugin, "插件必须实现Plugin接口")

            // 场景2:驱动程序验证
            driver := GetDriver("mysql")
            a.Implements((*Driver)(nil), driver, "驱动必须实现Driver接口")

            // 场景3:中间件验证
            middleware := CreateMiddleware()
            a.Implements((*Middleware)(nil), middleware)
        }

        func TestNotImplements(t *testing.T) {
            a := assert.New(t)

            type Writer interface {
                Write(p []byte) (n int, err error)
            }

            type SimpleStruct struct {
                Value int
            }

            obj := &SimpleStruct{Value: 42}

            // SimpleStruct不实现Writer接口
            a.NotImplements((*Writer)(nil), obj)
        }

        // 辅助接口和类型
        type Plugin interface {
            Init() error
            Execute() error
        }

        type Driver interface {
            Connect() error
            Query(sql string) (interface{}, error)
        }

        type Middleware interface {
            Handle(next func()) error
        }

        type UserPlugin struct{}

        func (p *UserPlugin) Init() error       { return nil }
        func (p *UserPlugin) Execute() error    { return nil }

        func LoadPlugin(name string) Plugin {
            return &UserPlugin{}
        }

        type MySQLDriver struct{}

        func (d *MySQLDriver) Connect() error                            { return nil }
        func (d *MySQLDriver) Query(sql string) (interface{}, error)     { return nil, nil }

        func GetDriver(name string) Driver {
            return &MySQLDriver{}
        }

        type SimpleMiddleware struct{}

        func (m *SimpleMiddleware) Handle(next func()) error { return nil }

        func CreateMiddleware() Middleware {
            return &SimpleMiddleware{}
        }
        ---

03.Kind检查
    a.reflect.Kind
        虽然testify没有直接的Kind断言,但可以结合reflect.Kind进行类型分类检查。Kind表示类型的基础分类如Int、String、Struct、Ptr等。Kind检查适合需要判断类型大类而非精确类型的场景。
    b.Kind使用
        ---
        // Kind检查示例
        package kind

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

        func TestKindChecks(t *testing.T) {
            a := assert.New(t)

            // 使用reflect.Kind检查类型分类
            a.Equal(reflect.Int, reflect.TypeOf(42).Kind())
            a.Equal(reflect.String, reflect.TypeOf("hello").Kind())
            a.Equal(reflect.Bool, reflect.TypeOf(true).Kind())
            a.Equal(reflect.Float64, reflect.TypeOf(3.14).Kind())

            // 结构体
            type User struct{ Name string }
            a.Equal(reflect.Struct, reflect.TypeOf(User{}).Kind())

            // 指针
            user := &User{}
            a.Equal(reflect.Ptr, reflect.TypeOf(user).Kind())

            // 切片
            a.Equal(reflect.Slice, reflect.TypeOf([]int{}).Kind())

            // map
            a.Equal(reflect.Map, reflect.TypeOf(map[string]int{}).Kind())
        }

        func TestKindScenarios(t *testing.T) {
            a := assert.New(t)

            // 场景:动态类型检查
            data := GetDynamicData()
            kind := reflect.TypeOf(data).Kind()

            switch kind {
            case reflect.Slice:
                a.True(true, "数据是切片类型")
            case reflect.Map:
                a.True(false, "数据不应该是map类型")
            default:
                a.Fail("未知的数据类型")
            }
        }

        func GetDynamicData() interface{} {
            return []int{1, 2, 3}
        }
        ---

3.5 错误断言

01.Error和NoError断言
    a.功能说明
        Error断言检查错误不为nil,NoError检查错误为nil。这是最基础也是最常用的错误检查方法。在Go语言中,错误处理是显式的,几乎每个可能失败的操作都会返回error,因此错误断言在测试中无处不在。
    b.使用场景
        NoError用于验证操作成功完成,没有产生错误。Error用于验证错误场景,确保函数正确地返回了错误。两者配合使用可以完整测试函数的正常路径和异常路径。
    c.代码示例
        ---
        // Error和NoError断言详解
        package error

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

        func TestNoError(t *testing.T) {
            a := assert.New(t)

            // 成功操作,无错误
            err := DoSomethingSuccess()
            a.NoError(err)

            // 文件操作成功
            file, err := os.Open("testfile.txt")
            if err == nil {
                defer file.Close()
                a.NoError(err)
            }

            // 数据验证成功
            user := &User{Name: "Alice", Age: 25}
            err = ValidateUser(user)
            a.NoError(err, "有效用户不应该产生错误")
        }

        func TestError(t *testing.T) {
            a := assert.New(t)

            // 失败操作,有错误
            err := DoSomethingFail()
            a.Error(err, "应该返回错误")

            // 文件不存在
            _, err = os.Open("nonexistent.txt")
            a.Error(err)

            // 数据验证失败
            invalidUser := &User{Name: "", Age: -1}
            err = ValidateUser(invalidUser)
            a.Error(err, "无效用户应该产生错误")
        }

        func TestErrorScenarios(t *testing.T) {
            a := assert.New(t)

            // 场景1:数据库操作
            db := ConnectDB()
            if db != nil {
                defer db.Close()

                err := db.Insert(&User{Name: "Alice"})
                a.NoError(err, "插入应该成功")

                _, err = db.Query("invalid sql")
                a.Error(err, "无效SQL应该返回错误")
            }

            // 场景2:API调用
            response, err := CallAPI("valid-endpoint")
            a.NoError(err)
            a.NotNil(response)

            _, err = CallAPI("invalid-endpoint")
            a.Error(err)

            // 场景3:文件处理
            err = ProcessFile("valid.txt")
            a.NoError(err)

            err = ProcessFile("invalid.txt")
            a.Error(err)
        }

        type User struct {
            Name string
            Age  int
        }

        func DoSomethingSuccess() error {
            return nil
        }

        func DoSomethingFail() error {
            return errors.New("operation failed")
        }

        func ValidateUser(user *User) error {
            if user.Name == "" {
                return errors.New("name is required")
            }
            if user.Age < 0 {
                return errors.New("age must be positive")
            }
            return nil
        }

        type DB struct{}

        func ConnectDB() *DB           { return &DB{} }
        func (db *DB) Close()           {}
        func (db *DB) Insert(u *User) error { return nil }
        func (db *DB) Query(sql string) (interface{}, error) {
            if sql == "invalid sql" {
                return nil, errors.New("syntax error")
            }
            return nil, nil
        }

        func CallAPI(endpoint string) (interface{}, error) {
            if endpoint == "invalid-endpoint" {
                return nil, errors.New("404 not found")
            }
            return map[string]string{"status": "ok"}, nil
        }

        func ProcessFile(filename string) error {
            if filename == "invalid.txt" {
                return errors.New("file not found")
            }
            return nil
        }
        ---

02.EqualError断言
    a.功能说明
        EqualError断言检查错误消息是否与指定字符串完全相等。这用于精确验证错误消息内容,确保错误信息正确且一致。EqualError比Error更严格,不仅要求有错误,还要求错误消息精确匹配。
    b.使用场景
        EqualError适合测试自定义错误类型,验证错误消息格式,确保API错误响应一致。在测试用户可见的错误消息时特别有用,可以确保错误提示清晰准确。
    c.代码示例
        ---
        // EqualError断言详解
        package equalerror

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

        func TestEqualError(t *testing.T) {
            a := assert.New(t)

            // 精确匹配错误消息
            err := errors.New("file not found")
            a.EqualError(err, "file not found")

            // 格式化错误消息
            filename := "test.txt"
            err2 := fmt.Errorf("failed to open %s", filename)
            a.EqualError(err2, "failed to open test.txt")
        }

        func TestEqualErrorValidation(t *testing.T) {
            a := assert.New(t)

            // 验证错误消息
            err := ValidateEmail("invalid")
            a.EqualError(err, "invalid email format")

            err = ValidateAge(-1)
            a.EqualError(err, "age must be between 0 and 150")

            err = ValidatePassword("123")
            a.EqualError(err, "password must be at least 8 characters")
        }

        func TestEqualErrorCustomTypes(t *testing.T) {
            a := assert.New(t)

            // 自定义错误类型
            type ValidationError struct {
                Field   string
                Message string
            }

            func (e *ValidationError) Error() string {
                return fmt.Sprintf("%s: %s", e.Field, e.Message)
            }

            err := &ValidationError{
                Field:   "email",
                Message: "invalid format",
            }

            a.EqualError(err, "email: invalid format")
        }

        func ValidateEmail(email string) error {
            if !strings.Contains(email, "@") {
                return errors.New("invalid email format")
            }
            return nil
        }

        func ValidateAge(age int) error {
            if age < 0 || age > 150 {
                return errors.New("age must be between 0 and 150")
            }
            return nil
        }

        func ValidatePassword(password string) error {
            if len(password) < 8 {
                return errors.New("password must be at least 8 characters")
            }
            return nil
        }
        ---

03.ErrorIs和ErrorAs断言
    a.Go 1.13错误链
        Go 1.13引入了错误包装机制,使用fmt.Errorf的%w动词可以包装错误,形成错误链。ErrorIs检查错误链中是否包含特定错误,ErrorAs检查错误链中是否有可以转换为特定类型的错误。
    b.错误链场景
        错误包装用于添加上下文信息而不丢失原始错误。ErrorIs适合检查是否是特定的标准错误如io.EOF、os.ErrNotExist。ErrorAs适合提取自定义错误类型并访问其字段。
    c.代码示例
        ---
        // ErrorIs和ErrorAs断言详解
        package errorchain

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

        func TestErrorIs(t *testing.T) {
            a := assert.New(t)

            // 定义标准错误
            var ErrNotFound = errors.New("not found")
            var ErrPermission = errors.New("permission denied")

            // 包装错误
            err := fmt.Errorf("failed to get user: %w", ErrNotFound)

            // ErrorIs检查错误链
            a.ErrorIs(err, ErrNotFound)
            a.NotErrorIs(err, ErrPermission)

            // 多层包装
            wrappedTwice := fmt.Errorf("operation failed: %w", err)
            a.ErrorIs(wrappedTwice, ErrNotFound)  // 仍然能检测到
        }

        func TestErrorIsStandard(t *testing.T) {
            a := assert.New(t)

            // 标准库错误
            _, err := os.Open("nonexistent.txt")
            a.ErrorIs(err, os.ErrNotExist)

            // 包装标准错误
            wrappedErr := fmt.Errorf("failed to read config: %w", err)
            a.ErrorIs(wrappedErr, os.ErrNotExist)
        }

        func TestErrorAs(t *testing.T) {
            a := assert.New(t)

            // 自定义错误类型
            type ValidationError struct {
                Field   string
                Message string
                Code    int
            }

            func (e *ValidationError) Error() string {
                return fmt.Sprintf("[%d] %s: %s", e.Code, e.Field, e.Message)
            }

            // 创建并包装自定义错误
            valErr := &ValidationError{
                Field:   "email",
                Message: "invalid format",
                Code:    400,
            }

            wrappedErr := fmt.Errorf("validation failed: %w", valErr)

            // ErrorAs提取自定义错误
            var target *ValidationError
            a.ErrorAs(wrappedErr, &target)

            // 访问自定义错误的字段
            a.Equal("email", target.Field)
            a.Equal("invalid format", target.Message)
            a.Equal(400, target.Code)
        }

        func TestErrorChainScenarios(t *testing.T) {
            a := assert.New(t)

            // 场景1:数据库错误链
            var ErrDBConnection = errors.New("database connection failed")

            err := ConnectDatabase()
            a.ErrorIs(err, ErrDBConnection)

            // 场景2:多层业务错误
            err2 := ProcessOrder(123)
            if err2 != nil {
                var paymentErr *PaymentError
                if a.ErrorAs(err2, &paymentErr) {
                    a.Equal("insufficient funds", paymentErr.Reason)
                }
            }
        }

        var ErrDBConnection = errors.New("database connection failed")

        func ConnectDatabase() error {
            return fmt.Errorf("failed to initialize: %w", ErrDBConnection)
        }

        type PaymentError struct {
            Reason string
            Amount float64
        }

        func (e *PaymentError) Error() string {
            return fmt.Sprintf("payment failed: %s (amount: %.2f)", e.Reason, e.Amount)
        }

        func ProcessOrder(orderID int) error {
            payErr := &PaymentError{
                Reason: "insufficient funds",
                Amount: 99.99,
            }
            return fmt.Errorf("order %d: %w", orderID, payErr)
        }
        ---

04.ErrorContains断言
    a.功能说明
        ErrorContains断言检查错误消息是否包含指定的子字符串。相比EqualError的精确匹配,ErrorContains更灵活,只需要错误消息包含关键词即可。适合测试错误消息的关键信息而不关心完整格式。
    b.使用场景
        ErrorContains适合测试动态错误消息,验证错误消息包含关键信息,测试第三方库返回的错误。当错误消息格式可能变化但关键词保持不变时,使用ErrorContains比EqualError更稳定。
    c.代码示例
        ---
        // ErrorContains断言详解
        package errorcontains

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

        func TestErrorContains(t *testing.T) {
            a := assert.New(t)

            // 检查错误消息包含关键词
            err := errors.New("failed to connect to database: connection timeout")
            a.ErrorContains(err, "database")
            a.ErrorContains(err, "timeout")
            a.ErrorContains(err, "connect")

            // 动态错误消息
            filename := "config.json"
            err2 := fmt.Errorf("failed to read file %s: permission denied", filename)
            a.ErrorContains(err2, filename)
            a.ErrorContains(err2, "permission")
        }

        func TestErrorContainsScenarios(t *testing.T) {
            a := assert.New(t)

            // 场景1:API错误
            err := CallExternalAPI()
            if err != nil {
                a.ErrorContains(err, "API")
                a.ErrorContains(err, "timeout")
            }

            // 场景2:验证错误
            err = ValidateForm(map[string]string{})
            a.ErrorContains(err, "required")

            // 场景3:数据库错误
            err = QueryDatabase("invalid sql")
            a.ErrorContains(err, "syntax")
        }

        func CallExternalAPI() error {
            return fmt.Errorf("API call failed: request timeout after 30s")
        }

        func ValidateForm(form map[string]string) error {
            if form["name"] == "" {
                return errors.New("name field is required")
            }
            return nil
        }

        func QueryDatabase(sql string) error {
            return errors.New("SQL syntax error: unexpected token")
        }
        ---

01.Panics和NotPanics断言 a.功能说明 Panics断言检查函数执行时是否发生panic,NotPanics检查函数不会panic。在Go语言中,panic用于表示不可恢复的错误,测试panic行为确保程序在异常情况下的正确性。testify提供安全的方式来测试panic,避免测试进程崩溃。 b.Panic测试原理 Panics和NotPanics通过defer recover机制捕获panic。测试代码将待测函数包装在匿名函数中执行,通过recover捕获panic并验证。这使得panic测试安全可控,不会导致测试中断。 c.代码示例 ``` // Panics和NotPanics断言详解 package panic

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

    func TestPanicsBasics(t *testing.T) {
        a := assert.New(t)

        // 测试会panic的函数
        a.Panics(func() {
            panic("something went wrong")
        })

        // 测试不会panic的函数
        a.NotPanics(func() {
            _ = 1 + 1
        })

        // 除零会panic
        a.Panics(func() {
            x := 0
            _ = 10 / x
        })

        // 访问nil指针会panic
        a.Panics(func() {
            var ptr *int
            _ = *ptr
        })

        // 访问越界会panic
        a.Panics(func() {
            arr := []int{1, 2, 3}
            _ = arr[10]
        })
    }

    func TestPanicsScenarios(t *testing.T) {
        a := assert.New(t)

        // 场景1:参数验证导致panic
        a.Panics(func() {
            MustValidate(nil)
        }, "nil参数应该panic")

        // 场景2:初始化失败导致panic
        a.Panics(func() {
            InitializeWithInvalidConfig()
        })

        // 场景3:资源不可用导致panic
        a.Panics(func() {
            MustConnect("invalid-host")
        })
    }

    func TestNotPanicsScenarios(t *testing.T) {
        a := assert.New(t)

        // 场景1:正常参数不应panic
        a.NotPanics(func() {
            MustValidate(&User{Name: "Alice"})
        })

        // 场景2:有效配置不应panic
        a.NotPanics(func() {
            InitializeWithValidConfig()
        })

        // 场景3:正常操作不应panic
        a.NotPanics(func() {
            result := SafeOperation()
            _ = result
        })
    }

    func TestPanicRecovery(t *testing.T) {
        a := assert.New(t)

        // 测试panic恢复机制
        a.Panics(func() {
            defer func() {
                if r := recover(); r != nil {
                    // 恢复但重新panic以便测试捕获
                    panic(r)
                }
            }()
            panic("test panic")
        })
    }

    type User struct {
        Name string
    }

    func MustValidate(user *User) {
        if user == nil {
            panic("user cannot be nil")
        }
        if user.Name == "" {
            panic("name is required")
        }
    }

    func InitializeWithInvalidConfig() {
        panic("invalid configuration")
    }

    func InitializeWithValidConfig() {
        // 正常初始化
    }

    func MustConnect(host string) {
        if host == "invalid-host" {
            panic("failed to connect")
        }
    }

    func SafeOperation() int {
        return 42
    }
    ```

02.PanicValue断言 a.功能说明 PanicsWithValue断言不仅检查函数是否panic,还检查panic的值是否与指定值相等。PanicsWithError检查panic的值是否是error类型且消息匹配。这些方法用于精确验证panic的内容。 b.Panic值类型 Go语言的panic可以传递任何类型的值,常见的有string、error、自定义类型等。PanicsWithValue进行精确匹配,PanicsWithError专门处理error类型的panic。 c.代码示例 ``` // Panic值断言详解 package panicvalue

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

    func TestPanicsWithValue(t *testing.T) {
        a := assert.New(t)

        // 字符串panic值
        a.PanicsWithValue("expected panic", func() {
            panic("expected panic")
        })

        // 整数panic值
        a.PanicsWithValue(42, func() {
            panic(42)
        })

        // 结构体panic值
        type PanicData struct {
            Code    int
            Message string
        }

        expected := PanicData{Code: 500, Message: "server error"}
        a.PanicsWithValue(expected, func() {
            panic(expected)
        })
    }

    func TestPanicsWithError(t *testing.T) {
        a := assert.New(t)

        // error类型的panic
        expectedErr := errors.New("critical error")
        a.PanicsWithError(expectedErr, func() {
            panic(expectedErr)
        })

        // 自定义error类型
        type CustomError struct {
            Code int
            Msg  string
        }

        func (e *CustomError) Error() string {
            return e.Msg
        }

        customErr := &CustomError{Code: 404, Msg: "not found"}
        a.PanicsWithError(customErr, func() {
            panic(customErr)
        })
    }

    func TestPanicValueScenarios(t *testing.T) {
        a := assert.New(t)

        // 场景1:断言失败panic
        a.PanicsWithValue("assertion failed: x must be positive", func() {
            Assert(false, "x must be positive")
        })

        // 场景2:配置错误panic
        a.PanicsWithError(errors.New("invalid config"), func() {
            LoadConfig("invalid.yaml")
        })

        // 场景3:资源耗尽panic
        type ResourceError struct {
            Resource string
            Limit    int
        }

        expected := ResourceError{Resource: "memory", Limit: 1024}
        a.PanicsWithValue(expected, func() {
            AllocateResources(2048)
        })
    }

    func Assert(condition bool, message string) {
        if !condition {
            panic("assertion failed: " + message)
        }
    }

    func LoadConfig(filename string) {
        if filename == "invalid.yaml" {
            panic(errors.New("invalid config"))
        }
    }

    type ResourceError struct {
        Resource string
        Limit    int
    }

    func AllocateResources(size int) {
        if size > 1024 {
            panic(ResourceError{Resource: "memory", Limit: 1024})
        }
    }
    ```

03.安全panic测试 a.测试隔离 panic测试需要确保测试隔离,一个测试的panic不应该影响其他测试。testify的Panics断言通过recover机制实现隔离,每个panic测试在独立的goroutine中执行。 b.最佳实践 测试panic时应该明确panic的触发条件,验证panic消息或值的准确性,确保只在必要时使用panic。对于可恢复的错误应该使用error返回,只有真正不可恢复的情况才使用panic。 c.代码示例 ``` // 安全panic测试最佳实践 package safepanic

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

    func TestSafePanicTesting(t *testing.T) {
        a := assert.New(t)

        // 测试1:验证panic条件
        a.Panics(func() {
            ValidatePositive(-1)
        }, "负数应该panic")

        a.NotPanics(func() {
            ValidatePositive(1)
        }, "正数不应该panic")

        // 测试2:验证panic消息
        a.PanicsWithValue("value must be positive", func() {
            ValidatePositive(-1)
        })

        // 测试3:测试后程序继续运行
        a.Equal(42, 42, "测试在panic测试后继续")
    }

    func TestMultiplePanicTests(t *testing.T) {
        a := assert.New(t)

        // 多个panic测试互不影响
        a.Panics(func() { panic("test 1") })
        a.Panics(func() { panic("test 2") })
        a.Panics(func() { panic("test 3") })

        // 后续测试正常执行
        a.Equal(1+1, 2)
    }

    func TestPanicVsError(t *testing.T) {
        a := assert.New(t)

        // 推荐:可恢复错误使用error
        err := DoSomethingRecoverable()
        a.Error(err)

        // 只有不可恢复的情况才panic
        a.Panics(func() {
            DoSomethingUnrecoverable()
        })
    }

    func ValidatePositive(value int) {
        if value <= 0 {
            panic("value must be positive")
        }
    }

    func DoSomethingRecoverable() error {
        // 可恢复的错误返回error
        return errors.New("recoverable error")
    }

    func DoSomethingUnrecoverable() {
        // 不可恢复的错误使用panic
        panic("unrecoverable error")
    }
    ```

04.Panic测试模式 a.Must函数测试 Go语言中的Must函数模式在初始化失败时panic。测试Must函数需要验证正常情况不panic,异常情况正确panic。Must函数通常用于程序启动阶段的配置验证。 b.不变量断言 使用panic实现不变量断言,在违反程序不变量时panic。测试这类断言需要构造违反不变量的场景,验证panic被正确触发。 c.代码示例 ``` // Panic测试模式 package panicpattern

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

    // Must函数模式
    func MustCompile(pattern string) *regexp.Regexp {
        r, err := regexp.Compile(pattern)
        if err != nil {
            panic(err)
        }
        return r
    }

    func TestMustFunctions(t *testing.T) {
        a := assert.New(t)

        // 有效正则表达式不应panic
        a.NotPanics(func() {
            r := MustCompile(`^\d+$`)
            _ = r
        })

        // 无效正则表达式应该panic
        a.Panics(func() {
            _ = MustCompile(`[invalid`)
        })
    }

    // 不变量断言模式
    type Stack struct {
        items []int
        size  int
    }

    func (s *Stack) Push(item int) {
        s.items = append(s.items, item)
        s.size++
        s.assertInvariant()
    }

    func (s *Stack) Pop() int {
        if s.size == 0 {
            panic("pop from empty stack")
        }
        item := s.items[s.size-1]
        s.items = s.items[:s.size-1]
        s.size--
        s.assertInvariant()
        return item
    }

    func (s *Stack) assertInvariant() {
        if len(s.items) != s.size {
            panic("invariant violated: size mismatch")
        }
    }

    func TestInvariantAssertions(t *testing.T) {
        a := assert.New(t)

        // 正常操作不应违反不变量
        a.NotPanics(func() {
            s := &Stack{}
            s.Push(1)
            s.Push(2)
            _ = s.Pop()
        })

        // 从空栈pop应该panic
        a.Panics(func() {
            s := &Stack{}
            s.Pop()
        })
    }
    ```

3.7 自定义断言

01.Condition断言
    a.功能说明
        Condition断言接受一个返回bool的函数,用于执行自定义的复杂条件检查。当testify提供的标准断言不能满足需求时,Condition提供了灵活的扩展方式。Condition让你可以编写任意复杂的验证逻辑,同时保持测试代码的可读性。
    b.使用场景
        Condition适用于需要多个条件组合判断的场景,复杂的业务规则验证,需要访问多个对象属性的检查,以及标准断言无法表达的自定义逻辑。
    c.代码示例
        ---
        // Condition断言详解
        package condition

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

        func TestConditionBasics(t *testing.T) {
            a := assert.New(t)

            // 简单条件
            a.Condition(func() bool {
                return 5 > 3
            }, "5应该大于3")

            // 复杂条件
            a.Condition(func() bool {
                x := 10
                y := 20
                return x+y == 30 && x < y
            }, "复杂条件应该满足")
        }

        func TestConditionWithObjects(t *testing.T) {
            a := assert.New(t)

            user := User{
                Name:  "Alice",
                Age:   25,
                Email: "[email protected]",
            }

            // 验证多个字段
            a.Condition(func() bool {
                return len(user.Name) > 0 &&
                       user.Age >= 18 &&
                       strings.Contains(user.Email, "@")
            }, "用户信息应该完整且有效")
        }

        func TestConditionBusinessRules(t *testing.T) {
            a := assert.New(t)

            order := Order{
                Items:    []OrderItem{{Price: 100}, {Price: 50}},
                Discount: 10,
                Tax:      15,
            }

            // 复杂业务规则验证
            a.Condition(func() bool {
                subtotal := 0.0
                for _, item := range order.Items {
                    subtotal += item.Price
                }
                total := subtotal - order.Discount + order.Tax
                return total == 155.0
            }, "订单总额计算应该正确")
        }

        func TestConditionCollections(t *testing.T) {
            a := assert.New(t)

            numbers := []int{1, 2, 3, 4, 5}

            // 集合条件
            a.Condition(func() bool {
                // 所有元素都是正数
                for _, n := range numbers {
                    if n <= 0 {
                        return false
                    }
                }
                return true
            }, "所有数字应该是正数")

            // 集合统计条件
            a.Condition(func() bool {
                sum := 0
                for _, n := range numbers {
                    sum += n
                }
                return sum == 15
            }, "数字总和应该是15")
        }

        type User struct {
            Name  string
            Age   int
            Email string
        }

        type Order struct {
            Items    []OrderItem
            Discount float64
            Tax      float64
        }

        type OrderItem struct {
            Price float64
        }
        ---

02.自定义断言函数
    a.封装重复逻辑
        当某个断言逻辑需要在多个测试中重复使用时,可以将其封装为自定义断言函数。自定义断言函数接受testing.T和被测值作为参数,内部使用testify断言,提供清晰的错误信息。
    b.领域特定断言
        为特定领域创建专用的断言函数,如验证HTTP响应、数据库记录、配置对象等。领域特定断言让测试代码更加语义化,减少重复,提高可维护性。
    c.代码示例
        ---
        // 自定义断言函数
        package customassert

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

        // 自定义断言:验证用户有效性
        func AssertValidUser(t *testing.T, user *User) {
            t.Helper()
            a := assert.New(t)

            a.NotNil(user, "用户不能为nil")
            a.NotEmpty(user.Name, "用户名不能为空")
            a.Greater(user.Age, 0, "年龄必须大于0")
            a.Contains(user.Email, "@", "邮箱格式应该有效")
        }

        // 自定义断言:验证HTTP响应
        func AssertHTTPSuccess(t *testing.T, resp *http.Response) {
            t.Helper()
            a := assert.New(t)

            a.NotNil(resp)
            a.Equal(http.StatusOK, resp.StatusCode, "应该返回200")
            a.Contains(resp.Header.Get("Content-Type"), "application/json")
        }

        // 自定义断言:验证列表非空且有序
        func AssertSortedNonEmpty(t *testing.T, numbers []int) {
            t.Helper()
            a := assert.New(t)

            a.NotEmpty(numbers, "列表不应该为空")

            for i := 1; i < len(numbers); i++ {
                a.LessOrEqual(numbers[i-1], numbers[i],
                    "列表应该是有序的:索引%d的值(%d)应该<=索引%d的值(%d)",
                    i-1, numbers[i-1], i, numbers[i])
            }
        }

        // 使用自定义断言
        func TestCustomAssertions(t *testing.T) {
            // 使用自定义用户断言
            user := &User{
                Name:  "Alice",
                Age:   25,
                Email: "[email protected]",
            }
            AssertValidUser(t, user)

            // 使用自定义排序断言
            numbers := []int{1, 3, 5, 7, 9}
            AssertSortedNonEmpty(t, numbers)
        }

        // 自定义断言:验证订单
        func AssertValidOrder(t *testing.T, order *Order) {
            t.Helper()
            a := assert.New(t)

            a.NotNil(order)
            a.NotEmpty(order.Items, "订单必须包含商品")
            a.Positive(order.TotalAmount, "订单总额必须为正")
            a.NotEmpty(order.CustomerID, "必须指定客户")

            // 验证每个商品
            for i, item := range order.Items {
                a.Positive(item.Quantity, "商品%d数量必须为正", i)
                a.Positive(item.Price, "商品%d价格必须为正", i)
            }
        }

        type User struct {
            Name  string
            Age   int
            Email string
        }

        type Order struct {
            CustomerID  string
            Items       []OrderItem
            TotalAmount float64
        }

        type OrderItem struct {
            Quantity int
            Price    float64
        }
        ---

03.断言辅助函数
    a.复杂条件封装
        将复杂的条件判断逻辑封装为辅助函数,返回bool和错误描述。辅助函数可以在Condition断言中使用,也可以在自定义断言函数中使用。清晰的辅助函数让测试逻辑更易理解和维护。
    b.可重用验证器
        创建可重用的验证器函数,接受值并返回验证结果。验证器可以组合使用,形成复杂的验证链。这种模式在测试数据验证、配置检查等场景中特别有用。
    c.代码示例
        ---
        // 断言辅助函数
        package helper

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

        // 辅助函数:验证邮箱格式
        func isValidEmail(email string) (bool, string) {
            if !strings.Contains(email, "@") {
                return false, "邮箱必须包含@"
            }
            if !strings.Contains(email, ".") {
                return false, "邮箱必须包含域名"
            }
            parts := strings.Split(email, "@")
            if len(parts[0]) == 0 {
                return false, "邮箱用户名不能为空"
            }
            return true, ""
        }

        // 辅助函数:验证密码强度
        func isStrongPassword(password string) (bool, string) {
            if len(password) < 8 {
                return false, "密码长度至少8位"
            }

            hasUpper := false
            hasLower := false
            hasDigit := false

            for _, c := range password {
                if c >= 'A' && c <= 'Z' {
                    hasUpper = true
                } else if c >= 'a' && c <= 'z' {
                    hasLower = true
                } else if c >= '0' && c <= '9' {
                    hasDigit = true
                }
            }

            if !hasUpper {
                return false, "密码必须包含大写字母"
            }
            if !hasLower {
                return false, "密码必须包含小写字母"
            }
            if !hasDigit {
                return false, "密码必须包含数字"
            }

            return true, ""
        }

        // 使用辅助函数的自定义断言
        func AssertValidEmail(t *testing.T, email string) {
            t.Helper()
            valid, reason := isValidEmail(email)
            assert.True(t, valid, reason)
        }

        func AssertStrongPassword(t *testing.T, password string) {
            t.Helper()
            valid, reason := isStrongPassword(password)
            assert.True(t, valid, reason)
        }

        // 测试使用
        func TestValidationHelpers(t *testing.T) {
            // 有效邮箱
            AssertValidEmail(t, "[email protected]")

            // 强密码
            AssertStrongPassword(t, "MyP@ssw0rd")
        }

        // 组合验证器
        type Validator func(value interface{}) (bool, string)

        func CombineValidators(validators ...Validator) Validator {
            return func(value interface{}) (bool, string) {
                for _, validator := range validators {
                    if valid, reason := validator(value); !valid {
                        return false, reason
                    }
                }
                return true, ""
            }
        }

        // 使用组合验证器
        func TestCombinedValidators(t *testing.T) {
            a := assert.New(t)

            notEmpty := func(value interface{}) (bool, string) {
                str, ok := value.(string)
                if !ok || str == "" {
                    return false, "值不能为空"
                }
                return true, ""
            }

            minLength := func(min int) Validator {
                return func(value interface{}) (bool, string) {
                    str, ok := value.(string)
                    if !ok || len(str) < min {
                        return false, fmt.Sprintf("长度至少%d", min)
                    }
                    return true, ""
                }
            }

            // 组合多个验证器
            validator := CombineValidators(notEmpty, minLength(3))

            valid, reason := validator("hello")
            a.True(valid, reason)

            valid, reason = validator("hi")
            a.False(valid)
            a.Equal("长度至少3", reason)
        }
        ---

04.断言扩展模式
    a.流畅接口模式
        创建流畅接口风格的断言扩展,支持链式调用。流畅接口让断言代码读起来更像自然语言,提高可读性。这种模式适合创建领域特定的断言库。
    b.断言构建器
        使用构建器模式创建复杂的断言。构建器积累多个断言条件,最后统一执行验证。这种模式适合需要组合多个条件的复杂验证场景。
    c.代码示例
        ---
        // 断言扩展模式
        package extension

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

        // 流畅接口断言
        type UserAssertion struct {
            t    *testing.T
            user *User
        }

        func AssertUser(t *testing.T, user *User) *UserAssertion {
            return &UserAssertion{t: t, user: user}
        }

        func (ua *UserAssertion) IsNotNil() *UserAssertion {
            ua.t.Helper()
            assert.NotNil(ua.t, ua.user)
            return ua
        }

        func (ua *UserAssertion) HasName(name string) *UserAssertion {
            ua.t.Helper()
            assert.Equal(ua.t, name, ua.user.Name)
            return ua
        }

        func (ua *UserAssertion) HasAge(age int) *UserAssertion {
            ua.t.Helper()
            assert.Equal(ua.t, age, ua.user.Age)
            return ua
        }

        func (ua *UserAssertion) IsAdult() *UserAssertion {
            ua.t.Helper()
            assert.GreaterOrEqual(ua.t, ua.user.Age, 18)
            return ua
        }

        // 使用流畅接口
        func TestFluentAssertions(t *testing.T) {
            user := &User{Name: "Alice", Age: 25, Email: "[email protected]"}

            // 链式断言
            AssertUser(t, user).
                IsNotNil().
                HasName("Alice").
                HasAge(25).
                IsAdult()
        }

        // 断言构建器
        type AssertionBuilder struct {
            t          *testing.T
            conditions []func() bool
            messages   []string
        }

        func NewAssertionBuilder(t *testing.T) *AssertionBuilder {
            return &AssertionBuilder{t: t}
        }

        func (ab *AssertionBuilder) Add(condition func() bool, message string) *AssertionBuilder {
            ab.conditions = append(ab.conditions, condition)
            ab.messages = append(ab.messages, message)
            return ab
        }

        func (ab *AssertionBuilder) Assert() {
            ab.t.Helper()
            for i, condition := range ab.conditions {
                assert.True(ab.t, condition(), ab.messages[i])
            }
        }

        // 使用断言构建器
        func TestAssertionBuilder(t *testing.T) {
            user := &User{Name: "Alice", Age: 25, Email: "[email protected]"}

            NewAssertionBuilder(t).
                Add(func() bool { return user.Name != "" }, "用户名不能为空").
                Add(func() bool { return user.Age >= 18 }, "必须是成年人").
                Add(func() bool { return strings.Contains(user.Email, "@") }, "邮箱格式有效").
                Assert()
        }

        type User struct {
            Name  string
            Age   int
            Email string
        }
        ---

3.8 断言消息格式化

01.自定义错误消息
    a.消息参数
        testify的所有断言方法都支持可选的消息参数,用于在断言失败时提供额外的上下文信息。消息参数可以是简单字符串,也可以是格式化字符串配合多个参数,类似fmt.Printf的用法。清晰的错误消息能大幅提高测试失败时的诊断效率。
    b.消息格式化语法
        消息参数使用fmt包的格式化语法。第一个消息参数是格式字符串,后续参数是要插入的值。支持%s字符串、%d整数、%v通用格式、%+v详细格式等格式化动词。
    c.代码示例
        ---
        // 断言消息格式化详解
        package message

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

        func TestSimpleMessages(t *testing.T) {
            a := assert.New(t)

            // 简单字符串消息
            a.Equal(5, Add(2, 3), "加法运算应该正确")

            // 无消息(使用默认错误信息)
            a.NotNil(GetUser(1))

            // 空字符串消息
            a.True(IsValid(), "")
        }

        func TestFormattedMessages(t *testing.T) {
            a := assert.New(t)

            // 格式化消息:%s - 字符串
            username := "Alice"
            a.NotEmpty(username, "用户名%s不能为空", username)

            // 格式化消息:%d - 整数
            age := 25
            a.GreaterOrEqual(age, 18, "年龄%d必须大于等于18", age)

            // 格式化消息:%v - 通用格式
            user := User{Name: "Alice", Age: 25}
            a.NotNil(user, "用户对象%v不能为nil", user)

            // 格式化消息:%+v - 详细格式(包含字段名)
            a.Equal("Alice", user.Name, "用户对象%+v的名称不匹配", user)

            // 格式化消息:%T - 类型
            var data interface{} = "hello"
            a.IsType("", data, "期望类型string但得到%T", data)
        }

        func TestMultipleParameters(t *testing.T) {
            a := assert.New(t)

            // 多个参数
            x, y := 10, 20
            a.Less(x, y, "期望%d < %d但实际不是", x, y)

            // 复杂消息
            order := Order{ID: 123, Total: 99.99}
            expected := 99.99
            a.Equal(expected, order.Total,
                "订单ID=%d的总额应该是%.2f,但实际是%.2f",
                order.ID, expected, order.Total)
        }

        func TestContextualMessages(t *testing.T) {
            a := assert.New(t)

            // 场景1:循环中的断言
            numbers := []int{2, 4, 6, 8, 10}
            for i, n := range numbers {
                a.Equal(0, n%2, "索引%d的元素%d应该是偶数", i, n)
            }

            // 场景2:测试数据上下文
            testCases := []struct {
                input    int
                expected int
            }{
                {1, 2},
                {2, 4},
                {3, 6},
            }

            for _, tc := range testCases {
                result := Double(tc.input)
                a.Equal(tc.expected, result,
                    "Double(%d)应该返回%d,但得到%d",
                    tc.input, tc.expected, result)
            }
        }

        type User struct {
            Name string
            Age  int
        }

        type Order struct {
            ID    int
            Total float64
        }

        func Add(a, b int) int { return a + b }
        func GetUser(id int) *User { return &User{Name: "Alice"} }
        func IsValid() bool { return true }
        func Double(n int) int { return n * 2 }
        ---

02.错误消息最佳实践
    a.消息编写原则
        错误消息应该清晰说明期望是什么,实际得到了什么,以及为什么这是错误的。包含足够的上下文信息帮助快速定位问题。使用主动语态,避免模糊的描述。消息应该面向开发者,使用技术术语。
    b.消息内容指导
        好的消息包含操作上下文、输入参数、期望结果、实际结果。避免冗余信息,testify已经显示了期望值和实际值。重点说明业务含义而不是重复断言本身的内容。
    c.代码示例
        ---
        // 错误消息最佳实践
        package bestpractice

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

        func TestGoodMessages(t *testing.T) {
            a := assert.New(t)

            // ✅ 好的消息:说明业务含义
            age := 15
            a.GreaterOrEqual(age, 18, "用户必须是成年人才能注册")

            // ✅ 好的消息:包含上下文
            userID := 123
            user := GetUser(userID)
            a.NotNil(user, "数据库应该包含ID为%d的用户", userID)

            // ✅ 好的消息:说明影响
            balance := -100.0
            a.GreaterOrEqual(balance, 0.0, "余额不足,无法完成交易")

            // ✅ 好的消息:指出问题
            config := LoadConfig()
            a.NotEmpty(config.DatabaseURL, "配置文件缺少必需的database_url字段")
        }

        func TestBadMessages(t *testing.T) {
            a := assert.New(t)

            // ❌ 差的消息:过于简单
            age := 15
            // a.GreaterOrEqual(age, 18, "错误")

            // ❌ 差的消息:重复断言内容
            // a.Equal(5, result, "result应该等于5")

            // ❌ 差的消息:没有上下文
            // a.NotNil(user, "不能为nil")

            // ❌ 差的消息:信息不足
            // a.True(valid, "验证失败")

            // 改进:提供具体信息
            a.True(valid, "邮箱格式验证失败:必须包含@符号")
        }

        func TestMessagePatterns(t *testing.T) {
            a := assert.New(t)

            // 模式1:说明前置条件
            db := ConnectDB()
            a.NotNil(db, "数据库连接必须在测试前建立")

            // 模式2:说明期望行为
            orders := GetOrders(userID)
            a.NotEmpty(orders, "活跃用户应该至少有一个订单")

            // 模式3:说明数据要求
            email := user.Email
            a.Contains(email, "@", "用户邮箱必须是有效的邮箱地址")

            // 模式4:说明业务规则
            discount := CalculateDiscount(order)
            a.LessOrEqual(discount, 100.0, "折扣不能超过订单总额")
        }

        type User struct{ Email string }
        type Order struct{}
        type Config struct{ DatabaseURL string }

        var userID = 123
        var valid = true

        func GetUser(id int) *User { return &User{} }
        func LoadConfig() Config { return Config{} }
        func ConnectDB() interface{} { return &struct{}{} }
        func GetOrders(userID int) []Order { return []Order{} }
        func CalculateDiscount(order Order) float64 { return 10.0 }
        ---

03.国际化考虑
    a.语言选择
        错误消息应该使用团队的工作语言。中文团队使用中文消息,英文团队使用英文消息。保持一致性,不要混用多种语言。技术术语可以使用英文,但说明性文字使用工作语言。
    b.消息模板
        为常见的断言场景创建消息模板,确保消息风格统一。模板可以包含占位符,使用时填入具体值。这有助于保持团队代码风格的一致性。
    c.代码示例
        ---
        // 国际化和消息模板
        package i18n

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

        // 消息模板常量
        const (
            MsgUserNotFound      = "未找到ID为%d的用户"
            MsgInvalidAge        = "年龄%d无效,必须在%d到%d之间"
            MsgDatabaseError     = "数据库操作失败:%s"
            MsgConfigMissing     = "配置项%s缺失或为空"
            MsgPermissionDenied  = "用户%s没有%s权限"
        )

        func TestWithMessageTemplates(t *testing.T) {
            a := assert.New(t)

            // 使用模板
            userID := 999
            user := FindUser(userID)
            a.NotNil(user, MsgUserNotFound, userID)

            // 使用模板验证范围
            age := 200
            a.True(age >= 0 && age <= 150, MsgInvalidAge, age, 0, 150)

            // 使用模板说明权限
            username := "guest"
            permission := "admin"
            hasPermission := CheckPermission(username, permission)
            a.True(hasPermission, MsgPermissionDenied, username, permission)
        }

        // 中文消息示例
        func Test中文消息(t *testing.T) {
            a := assert.New(t)

            用户名 := "张三"
            年龄 := 25

            a.NotEmpty(用户名, "用户名不能为空")
            a.GreaterOrEqual(年龄, 18, "用户%s的年龄%d必须大于等于18", 用户名, 年龄)
        }

        // 英文消息示例
        func TestEnglishMessages(t *testing.T) {
            a := assert.New(t)

            username := "Alice"
            age := 25

            a.NotEmpty(username, "username cannot be empty")
            a.GreaterOrEqual(age, 18, "user %s age %d must be at least 18", username, age)
        }

        func FindUser(id int) *User {
            if id == 999 {
                return nil
            }
            return &User{}
        }

        func CheckPermission(user, perm string) bool {
            return user != "guest" || perm != "admin"
        }
        ---

04.调试友好的消息
    a.包含诊断信息
        在错误消息中包含有助于调试的信息,如当前状态、相关变量值、执行路径。帮助开发者快速理解失败原因,减少调试时间。特别是在CI环境中,详细的错误消息至关重要。
    b.分层次的信息
        简单场景使用简短消息,复杂场景使用详细消息。对于嵌套对象,考虑使用%+v显示完整结构。对于集合,可以显示前几个元素作为样本。
    c.代码示例
        ---
        // 调试友好的消息
        package debug

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

        func TestDebugFriendlyMessages(t *testing.T) {
            a := assert.New(t)

            // 场景1:显示相关状态
            order := Order{
                ID:     123,
                Status: "pending",
                Items:  []Item{{ID: 1}, {ID: 2}},
            }

            a.Equal("completed", order.Status,
                "订单ID=%d应该已完成,当前状态=%s,包含%d个商品",
                order.ID, order.Status, len(order.Items))

            // 场景2:显示完整对象
            user := User{
                ID:    1,
                Name:  "Alice",
                Email: "[email protected]",
                Roles: []string{"user", "admin"},
            }

            a.Contains(user.Roles, "superadmin",
                "用户应该有superadmin角色,用户信息:%+v", user)

            // 场景3:显示集合样本
            items := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
            a.Len(items, 5,
                "期望5个元素,实际%d个,前3个: %v",
                len(items), items[:min(3, len(items))])
        }

        func TestExecutionPath(t *testing.T) {
            a := assert.New(t)

            // 显示执行路径
            input := 10
            step1 := Validate(input)
            step2 := Transform(step1)
            result := Calculate(step2)

            expected := 42
            a.Equal(expected, result,
                "计算流程:输入=%d -> 验证=%d -> 转换=%d -> 结果=%d(期望%d)",
                input, step1, step2, result, expected)
        }

        func TestComparisonDetails(t *testing.T) {
            a := assert.New(t)

            expected := map[string]int{
                "apples":  5,
                "oranges": 3,
                "bananas": 7,
            }

            actual := map[string]int{
                "apples":  5,
                "oranges": 4,  // 不同
                "bananas": 7,
            }

            a.Equal(expected, actual,
                "库存不匹配\n期望: %v\n实际: %v",
                expected, actual)
        }

        type Order struct {
            ID     int
            Status string
            Items  []Item
        }

        type Item struct {
            ID int
        }

        type User struct {
            ID    int
            Name  string
            Email string
            Roles []string
        }

        func min(a, b int) int {
            if a < b {
                return a
            }
            return b
        }

        func Validate(n int) int   { return n }
        func Transform(n int) int  { return n * 2 }
        func Calculate(n int) int  { return n + 22 }
        ---

04.Mock系统详解

01.Mock概念 a.什么是Mock Mock是测试中用来模拟真实对象行为的替代品。在单元测试中,Mock对象可以替代依赖项如数据库、外部API、文件系统等,使测试更快速、可控且独立。testify的mock包提供了强大的Mock功能,支持方法调用记录、参数验证和返回值控制。 b.Mock的作用 Mock隔离被测代码与外部依赖,使测试专注于当前单元。通过Mock可以模拟难以复现的场景如网络错误、超时等。Mock还能验证方法调用次数、参数和顺序,确保代码按预期与依赖交互。使用Mock可以大幅提升测试速度,避免真实依赖的开销。 c.代码示例 ``` // Mock基础概念示例 package mockbasic

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

    // 定义接口
    type UserRepository interface {
        GetByID(id int) (*User, error)
        Save(user *User) error
        Delete(id int) error
    }

    // 创建Mock对象
    type MockUserRepository struct {
        mock.Mock
    }

    // 实现接口方法
    func (m *MockUserRepository) GetByID(id int) (*User, error) {
        args := m.Called(id)
        if args.Get(0) == nil {
            return nil, args.Error(1)
        }
        return args.Get(0).(*User), args.Error(1)
    }

    func (m *MockUserRepository) Save(user *User) error {
        args := m.Called(user)
        return args.Error(0)
    }

    func (m *MockUserRepository) Delete(id int) error {
        args := m.Called(id)
        return args.Error(0)
    }

    // 业务逻辑
    type UserService struct {
        repo UserRepository
    }

    func NewUserService(repo UserRepository) *UserService {
        return &UserService{repo: repo}
    }

    func (s *UserService) GetUser(id int) (*User, error) {
        return s.repo.GetByID(id)
    }

    func (s *UserService) CreateUser(name string) error {
        user := &User{Name: name}
        return s.repo.Save(user)
    }

    // 测试
    func TestUserService_GetUser(t *testing.T) {
        // 创建Mock
        mockRepo := new(MockUserRepository)

        // 设置期望
        expectedUser := &User{ID: 1, Name: "Alice"}
        mockRepo.On("GetByID", 1).Return(expectedUser, nil)

        // 创建服务
        service := NewUserService(mockRepo)

        // 执行
        user, err := service.GetUser(1)

        // 断言
        assert.NoError(t, err)
        assert.Equal(t, expectedUser, user)

        // 验证Mock调用
        mockRepo.AssertExpectations(t)
    }

    func TestUserService_CreateUser(t *testing.T) {
        mockRepo := new(MockUserRepository)

        // 设置期望:Save方法被调用一次,返回nil(成功)
        mockRepo.On("Save", mock.Anything).Return(nil)

        service := NewUserService(mockRepo)

        err := service.CreateUser("Bob")

        assert.NoError(t, err)
        mockRepo.AssertExpectations(t)
    }

    type User struct {
        ID   int
        Name string
    }
    ```

02.Mock对象创建 a.嵌入mock.Mock 创建Mock对象的第一步是定义一个结构体,嵌入mock.Mock类型。这为结构体提供了Mock的核心功能如Called、On、AssertExpectations等方法。mock.Mock必须通过组合方式使用,不能直接实例化。 b.实现接口方法 Mock结构体需要实现目标接口的所有方法。每个方法内部调用m.Called()传入参数,然后从返回的Arguments中提取结果。testify会记录所有调用并与预设的期望进行匹配。 c.代码示例 ``` // Mock对象创建详解 package mockcreation

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

    // 场景1:简单接口Mock
    type Calculator interface {
        Add(a, b int) int
        Divide(a, b int) (int, error)
    }

    type MockCalculator struct {
        mock.Mock
    }

    func (m *MockCalculator) Add(a, b int) int {
        args := m.Called(a, b)
        return args.Int(0)
    }

    func (m *MockCalculator) Divide(a, b int) (int, error) {
        args := m.Called(a, b)
        return args.Int(0), args.Error(1)
    }

    // 场景2:复杂返回值Mock
    type DataService interface {
        Query(ctx context.Context, sql string, params []interface{}) ([]map[string]interface{}, error)
        Execute(ctx context.Context, sql string) (affected int64, err error)
    }

    type MockDataService struct {
        mock.Mock
    }

    func (m *MockDataService) Query(ctx context.Context, sql string, params []interface{}) ([]map[string]interface{}, error) {
        args := m.Called(ctx, sql, params)
        if args.Get(0) == nil {
            return nil, args.Error(1)
        }
        return args.Get(0).([]map[string]interface{}), args.Error(1)
    }

    func (m *MockDataService) Execute(ctx context.Context, sql string) (int64, error) {
        args := m.Called(ctx, sql)
        return args.Get(0).(int64), args.Error(1)
    }

    // 场景3:可变参数Mock
    type Logger interface {
        Log(level string, msg string, fields ...interface{})
        Logf(level string, format string, args ...interface{})
    }

    type MockLogger struct {
        mock.Mock
    }

    func (m *MockLogger) Log(level string, msg string, fields ...interface{}) {
        args := []interface{}{level, msg}
        args = append(args, fields...)
        m.Called(args...)
    }

    func (m *MockLogger) Logf(level string, format string, args ...interface{}) {
        callArgs := []interface{}{level, format}
        callArgs = append(callArgs, args...)
        m.Called(callArgs...)
    }

    // 测试示例
    func TestMockCreation(t *testing.T) {
        // 测试简单Mock
        calc := new(MockCalculator)
        calc.On("Add", 2, 3).Return(5)

        result := calc.Add(2, 3)
        assert.Equal(t, 5, result)
        calc.AssertExpectations(t)

        // 测试复杂返回值Mock
        ds := new(MockDataService)
        ctx := context.Background()
        expectedRows := []map[string]interface{}{
            {"id": 1, "name": "Alice"},
            {"id": 2, "name": "Bob"},
        }
        ds.On("Query", ctx, "SELECT * FROM users", []interface{}{}).
            Return(expectedRows, nil)

        rows, err := ds.Query(ctx, "SELECT * FROM users", []interface{}{})
        assert.NoError(t, err)
        assert.Equal(t, expectedRows, rows)
        ds.AssertExpectations(t)
    }
    ```

03.Mock生命周期 a.创建和初始化 使用new()或&MockType{}创建Mock对象实例。创建后立即使用On方法设置期望。一个Mock对象可以在同一个测试中设置多个期望,支持不同的方法调用。 b.使用和验证 将Mock对象注入到被测代码中。代码执行时,Mock会记录所有方法调用。测试结束时调用AssertExpectations验证所有期望的方法都被正确调用。如果有未满足的期望或意外的调用,测试会失败。 c.代码示例 ``` // Mock生命周期管理 package mocklifecycle

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

    type EmailService interface {
        Send(to, subject, body string) error
        SendBatch(recipients []string, subject, body string) error
    }

    type MockEmailService struct {
        mock.Mock
    }

    func (m *MockEmailService) Send(to, subject, body string) error {
        args := m.Called(to, subject, body)
        return args.Error(0)
    }

    func (m *MockEmailService) SendBatch(recipients []string, subject, body string) error {
        args := m.Called(recipients, subject, body)
        return args.Error(0)
    }

    type NotificationService struct {
        emailService EmailService
    }

    func (s *NotificationService) NotifyUser(email, message string) error {
        return s.emailService.Send(email, "Notification", message)
    }

    func (s *NotificationService) NotifyAll(emails []string, message string) error {
        return s.emailService.SendBatch(emails, "Broadcast", message)
    }

    func TestMockLifecycle(t *testing.T) {
        // 1. 创建阶段
        emailMock := new(MockEmailService)

        // 2. 设置期望阶段
        emailMock.On("Send", "[email protected]", "Notification", "Hello").
            Return(nil)

        // 3. 注入阶段
        service := &NotificationService{emailService: emailMock}

        // 4. 执行阶段
        err := service.NotifyUser("[email protected]", "Hello")

        // 5. 断言阶段
        assert.NoError(t, err)

        // 6. 验证期望阶段
        emailMock.AssertExpectations(t)
    }

    func TestMultipleExpectations(t *testing.T) {
        emailMock := new(MockEmailService)

        // 设置多个期望
        emailMock.On("Send", "[email protected]", "Notification", "Hi Alice").
            Return(nil)
        emailMock.On("Send", "[email protected]", "Notification", "Hi Bob").
            Return(nil)

        service := &NotificationService{emailService: emailMock}

        // 多次调用
        err1 := service.NotifyUser("[email protected]", "Hi Alice")
        err2 := service.NotifyUser("[email protected]", "Hi Bob")

        assert.NoError(t, err1)
        assert.NoError(t, err2)

        // 验证所有期望
        emailMock.AssertExpectations(t)
    }

    func TestMockReuse(t *testing.T) {
        // 注意:不建议在多个测试间共享Mock对象
        // 每个测试应该创建自己的Mock

        t.Run("Test1", func(t *testing.T) {
            emailMock := new(MockEmailService)
            emailMock.On("Send", mock.Anything, mock.Anything, mock.Anything).
                Return(nil).
                Once()

            service := &NotificationService{emailService: emailMock}
            err := service.NotifyUser("[email protected]", "Test")

            assert.NoError(t, err)
            emailMock.AssertExpectations(t)
        })

        t.Run("Test2", func(t *testing.T) {
            // 创建新的Mock实例
            emailMock := new(MockEmailService)
            emailMock.On("SendBatch", mock.Anything, mock.Anything, mock.Anything).
                Return(nil)

            service := &NotificationService{emailService: emailMock}
            err := service.NotifyAll([]string{"[email protected]", "[email protected]"}, "Batch")

            assert.NoError(t, err)
            emailMock.AssertExpectations(t)
        })
    }
    ```

01.On方法基础 a.On方法语法 On方法用于设置Mock对象的期望行为。基本语法是mock.On(“方法名”, 参数列表).Return(返回值列表)。参数列表必须与实际调用时的参数类型和数量匹配。On方法返回一个Call对象,可以链式调用其他方法如Return、Times、Once等进行更详细的配置。 b.参数匹配规则 On方法的参数支持精确匹配和模糊匹配。精确匹配要求参数值完全相同。模糊匹配使用mock.Anything、mock.AnythingOfType等匹配器。多个On调用可以针对同一方法设置不同参数的期望,testify会根据实际调用选择匹配的期望。 c.代码示例 ``` // 期望设置基础 package expectation

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

    type StorageService interface {
        Get(key string) (string, error)
        Set(key, value string) error
        Delete(key string) error
        Exists(key string) bool
    }

    type MockStorageService struct {
        mock.Mock
    }

    func (m *MockStorageService) Get(key string) (string, error) {
        args := m.Called(key)
        return args.String(0), args.Error(1)
    }

    func (m *MockStorageService) Set(key, value string) error {
        args := m.Called(key, value)
        return args.Error(0)
    }

    func (m *MockStorageService) Delete(key string) error {
        args := m.Called(key)
        return args.Error(0)
    }

    func (m *MockStorageService) Exists(key string) bool {
        args := m.Called(key)
        return args.Bool(0)
    }

    func TestBasicExpectation(t *testing.T) {
        storage := new(MockStorageService)

        // 基本期望设置
        storage.On("Get", "username").Return("Alice", nil)
        storage.On("Set", "username", "Bob").Return(nil)
        storage.On("Delete", "oldkey").Return(nil)
        storage.On("Exists", "config").Return(true)

        // 执行并验证
        value, err := storage.Get("username")
        assert.NoError(t, err)
        assert.Equal(t, "Alice", value)

        err = storage.Set("username", "Bob")
        assert.NoError(t, err)

        err = storage.Delete("oldkey")
        assert.NoError(t, err)

        exists := storage.Exists("config")
        assert.True(t, exists)

        storage.AssertExpectations(t)
    }

    func TestMultipleExpectations(t *testing.T) {
        storage := new(MockStorageService)

        // 同一方法,不同参数的多个期望
        storage.On("Get", "user:1").Return("Alice", nil)
        storage.On("Get", "user:2").Return("Bob", nil)
        storage.On("Get", "user:3").Return("", nil)

        // testify会根据参数自动匹配
        val1, _ := storage.Get("user:1")
        val2, _ := storage.Get("user:2")
        val3, _ := storage.Get("user:3")

        assert.Equal(t, "Alice", val1)
        assert.Equal(t, "Bob", val2)
        assert.Equal(t, "", val3)

        storage.AssertExpectations(t)
    }

    func TestAnythingMatcher(t *testing.T) {
        storage := new(MockStorageService)

        // 使用mock.Anything匹配任意参数
        storage.On("Set", mock.Anything, mock.Anything).Return(nil)
        storage.On("Get", mock.Anything).Return("default", nil)

        // 任何参数都会匹配
        err := storage.Set("key1", "value1")
        assert.NoError(t, err)

        err = storage.Set("key2", "value2")
        assert.NoError(t, err)

        value, _ := storage.Get("anykey")
        assert.Equal(t, "default", value)
    }

    func TestExpectationPriority(t *testing.T) {
        storage := new(MockStorageService)

        // 精确匹配优先于模糊匹配
        storage.On("Get", "special").Return("SPECIAL", nil)
        storage.On("Get", mock.Anything).Return("default", nil)

        // "special"匹配精确期望
        special, _ := storage.Get("special")
        assert.Equal(t, "SPECIAL", special)

        // 其他键匹配模糊期望
        other, _ := storage.Get("other")
        assert.Equal(t, "default", other)

        storage.AssertExpectations(t)
    }
    ```

02.Return方法详解 a.返回单个值 Return方法指定Mock方法被调用时的返回值。参数数量和类型必须与接口方法的返回值匹配。对于单返回值方法,传入一个参数。对于多返回值方法,传入多个参数。nil可以用于指针和error类型。 b.返回多个值 Go方法常返回多个值,最后一个通常是error。Return方法按顺序接收所有返回值。例如Return(“result”, nil)表示返回字符串”result”和nil错误。返回值类型必须与接口定义完全匹配。 c.代码示例 ``` // Return方法详解 package returnmethod

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

    type DataService interface {
        FetchOne(id int) (*Record, error)
        FetchMany(ids []int) ([]*Record, error)
        Count() int
        Process(data []byte) (result []byte, count int, err error)
    }

    type MockDataService struct {
        mock.Mock
    }

    func (m *MockDataService) FetchOne(id int) (*Record, error) {
        args := m.Called(id)
        if args.Get(0) == nil {
            return nil, args.Error(1)
        }
        return args.Get(0).(*Record), args.Error(1)
    }

    func (m *MockDataService) FetchMany(ids []int) ([]*Record, error) {
        args := m.Called(ids)
        if args.Get(0) == nil {
            return nil, args.Error(1)
        }
        return args.Get(0).([]*Record), args.Error(1)
    }

    func (m *MockDataService) Count() int {
        args := m.Called()
        return args.Int(0)
    }

    func (m *MockDataService) Process(data []byte) ([]byte, int, error) {
        args := m.Called(data)
        return args.Get(0).([]byte), args.Int(1), args.Error(2)
    }

    type Record struct {
        ID   int
        Name string
    }

    func TestReturnSingleValue(t *testing.T) {
        ds := new(MockDataService)

        // 返回单个值
        ds.On("Count").Return(42)

        count := ds.Count()
        assert.Equal(t, 42, count)

        ds.AssertExpectations(t)
    }

    func TestReturnTwoValues(t *testing.T) {
        ds := new(MockDataService)

        // 返回两个值:对象和error
        record := &Record{ID: 1, Name: "Alice"}
        ds.On("FetchOne", 1).Return(record, nil)

        // 返回nil和error
        ds.On("FetchOne", 999).Return(nil, errors.New("not found"))

        // 测试成功场景
        r, err := ds.FetchOne(1)
        assert.NoError(t, err)
        assert.Equal(t, record, r)

        // 测试错误场景
        r, err = ds.FetchOne(999)
        assert.Error(t, err)
        assert.Nil(t, r)

        ds.AssertExpectations(t)
    }

    func TestReturnMultipleValues(t *testing.T) {
        ds := new(MockDataService)

        // 返回三个值
        input := []byte("input")
        output := []byte("output")
        ds.On("Process", input).Return(output, 10, nil)

        result, count, err := ds.Process(input)
        assert.NoError(t, err)
        assert.Equal(t, output, result)
        assert.Equal(t, 10, count)

        ds.AssertExpectations(t)
    }

    func TestReturnSliceAndError(t *testing.T) {
        ds := new(MockDataService)

        // 返回切片
        records := []*Record{
            {ID: 1, Name: "Alice"},
            {ID: 2, Name: "Bob"},
        }
        ds.On("FetchMany", []int{1, 2}).Return(records, nil)

        // 返回空切片
        ds.On("FetchMany", []int{}).Return([]*Record{}, nil)

        // 返回nil切片和error
        ds.On("FetchMany", []int{999}).Return(nil, errors.New("error"))

        // 测试
        r1, err1 := ds.FetchMany([]int{1, 2})
        assert.NoError(t, err1)
        assert.Len(t, r1, 2)

        r2, err2 := ds.FetchMany([]int{})
        assert.NoError(t, err2)
        assert.Len(t, r2, 0)

        r3, err3 := ds.FetchMany([]int{999})
        assert.Error(t, err3)
        assert.Nil(t, r3)

        ds.AssertExpectations(t)
    }
    ```

03.调用次数控制 a.Once和Times Once()限制方法只能被调用一次。Times(n)限制方法必须被调用n次。如果不指定次数,默认期望至少被调用一次。这些方法用于精确控制和验证方法调用频率,确保代码按预期执行。 b.Maybe和其他选项 Maybe()表示方法可能被调用也可能不被调用,不会导致测试失败。这适用于可选的操作如日志记录。还可以使用AtLeast、AtMost等方法设置范围。 c.代码示例 ``` // 调用次数控制 package callcount

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

    type CacheService interface {
        Get(key string) (string, bool)
        Set(key, value string)
        Invalidate(key string)
        Clear()
    }

    type MockCacheService struct {
        mock.Mock
    }

    func (m *MockCacheService) Get(key string) (string, bool) {
        args := m.Called(key)
        return args.String(0), args.Bool(1)
    }

    func (m *MockCacheService) Set(key, value string) {
        m.Called(key, value)
    }

    func (m *MockCacheService) Invalidate(key string) {
        m.Called(key)
    }

    func (m *MockCacheService) Clear() {
        m.Called()
    }

    func TestOnce(t *testing.T) {
        cache := new(MockCacheService)

        // 期望被调用一次
        cache.On("Get", "config").Return("value", true).Once()

        // 第一次调用成功
        value, ok := cache.Get("config")
        assert.True(t, ok)
        assert.Equal(t, "value", value)

        // 如果再次调用会失败(因为只期望一次)
        // cache.Get("config") // 这会导致测试失败

        cache.AssertExpectations(t)
    }

    func TestTimes(t *testing.T) {
        cache := new(MockCacheService)

        // 期望被调用3次
        cache.On("Set", mock.Anything, mock.Anything).Times(3)

        cache.Set("key1", "value1")
        cache.Set("key2", "value2")
        cache.Set("key3", "value3")

        // 正好3次,验证通过
        cache.AssertExpectations(t)
    }

    func TestMaybe(t *testing.T) {
        cache := new(MockCacheService)

        // 可能被调用也可能不被调用
        cache.On("Clear").Maybe().Return()
        cache.On("Get", "optional").Maybe().Return("", false)

        // 不调用也不会失败
        cache.AssertExpectations(t)
    }

    func TestMaybeWithCall(t *testing.T) {
        cache := new(MockCacheService)

        cache.On("Invalidate", "temp").Maybe().Return()

        // 调用了也不会失败
        cache.Invalidate("temp")

        cache.AssertExpectations(t)
    }

    func TestMultipleCallsPattern(t *testing.T) {
        cache := new(MockCacheService)

        // 设置会被调用多次的期望
        cache.On("Get", "user:123").Return("Alice", true).Times(3)
        cache.On("Set", "user:123", mock.Anything).Once()

        // 第一次Get
        value1, _ := cache.Get("user:123")
        assert.Equal(t, "Alice", value1)

        // 更新
        cache.Set("user:123", "Alice Updated")

        // 再次Get(2次)
        value2, _ := cache.Get("user:123")
        value3, _ := cache.Get("user:123")
        assert.Equal(t, "Alice", value2)
        assert.Equal(t, "Alice", value3)

        cache.AssertExpectations(t)
    }

    func TestUnlimitedCalls(t *testing.T) {
        cache := new(MockCacheService)

        // 不限制调用次数(至少一次)
        cache.On("Get", "frequent").Return("value", true)

        // 可以调用任意多次
        for i := 0; i < 100; i++ {
            cache.Get("frequent")
        }

        cache.AssertExpectations(t)
    }
    ```

04.条件期望 a.运行时函数 可以使用Run方法在Mock方法被调用时执行自定义逻辑。这对于模拟副作用、验证参数、动态修改状态等场景很有用。Run接收一个函数,函数参数是Arguments对象,可以从中提取调用参数。 b.Return函数 ReturnFunc允许根据输入参数动态计算返回值。不同于固定的Return,ReturnFunc在每次调用时都会执行,可以根据参数返回不同的结果。这对于模拟复杂的业务逻辑很有帮助。 c.代码示例 ``` // 条件期望和动态行为 package conditional

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

    type TransformService interface {
        Transform(input string) (string, error)
        Validate(data interface{}) bool
        Process(items []string) []string
    }

    type MockTransformService struct {
        mock.Mock
    }

    func (m *MockTransformService) Transform(input string) (string, error) {
        args := m.Called(input)
        return args.String(0), args.Error(1)
    }

    func (m *MockTransformService) Validate(data interface{}) bool {
        args := m.Called(data)
        return args.Bool(0)
    }

    func (m *MockTransformService) Process(items []string) []string {
        args := m.Called(items)
        return args.Get(0).([]string)
    }

    func TestRunMethod(t *testing.T) {
        service := new(MockTransformService)

        var capturedInput string

        // 使用Run捕获参数
        service.On("Transform", mock.Anything).
            Run(func(args mock.Arguments) {
                capturedInput = args.String(0)
                fmt.Printf("Transform called with: %s\n", capturedInput)
            }).
            Return("result", nil)

        service.Transform("test input")

        assert.Equal(t, "test input", capturedInput)
        service.AssertExpectations(t)
    }

    func TestReturnFunc(t *testing.T) {
        service := new(MockTransformService)

        // 根据输入动态返回结果
        service.On("Transform", mock.Anything).
            Return(func(input string) string {
                return strings.ToUpper(input)
            }, func(input string) error {
                if input == "" {
                    return fmt.Errorf("empty input")
                }
                return nil
            })

        // 测试不同输入
        result1, err1 := service.Transform("hello")
        assert.NoError(t, err1)
        assert.Equal(t, "HELLO", result1)

        result2, err2 := service.Transform("world")
        assert.NoError(t, err2)
        assert.Equal(t, "WORLD", result2)

        _, err3 := service.Transform("")
        assert.Error(t, err3)

        service.AssertExpectations(t)
    }

    func TestConditionalValidation(t *testing.T) {
        service := new(MockTransformService)

        // 根据参数类型返回不同结果
        service.On("Validate", mock.Anything).
            Return(func(data interface{}) bool {
                switch v := data.(type) {
                case string:
                    return v != ""
                case int:
                    return v > 0
                default:
                    return false
                }
            })

        assert.True(t, service.Validate("hello"))
        assert.False(t, service.Validate(""))
        assert.True(t, service.Validate(10))
        assert.False(t, service.Validate(-5))

        service.AssertExpectations(t)
    }

    func TestComplexDynamicBehavior(t *testing.T) {
        service := new(MockTransformService)

        callCount := 0

        // 每次调用返回不同结果
        service.On("Process", mock.Anything).
            Return(func(items []string) []string {
                callCount++
                // 第一次调用:转换为大写
                if callCount == 1 {
                    result := make([]string, len(items))
                    for i, item := range items {
                        result[i] = strings.ToUpper(item)
                    }
                    return result
                }
                // 第二次调用:反转顺序
                if callCount == 2 {
                    result := make([]string, len(items))
                    for i, item := range items {
                        result[len(items)-1-i] = item
                    }
                    return result
                }
                // 后续调用:返回原样
                return items
            })

        input := []string{"a", "b", "c"}

        result1 := service.Process(input)
        assert.Equal(t, []string{"A", "B", "C"}, result1)

        result2 := service.Process(input)
        assert.Equal(t, []string{"c", "b", "a"}, result2)

        result3 := service.Process(input)
        assert.Equal(t, input, result3)

        service.AssertExpectations(t)
    }
    ```

01.精确匹配 a.值匹配原理 精确匹配要求Mock方法调用时的参数值与On方法中指定的参数值完全相同。对于基本类型如int、string、bool,使用==比较。对于指针和引用类型,比较的是地址而不是内容,这可能导致意外的不匹配。 b.类型匹配规则 参数类型必须严格匹配。即使值相同,类型不同也不会匹配。例如int(5)和int64(5)是不同的。对于接口类型,实际传入的具体类型必须一致。切片、映射等复合类型需要特别注意。 c.代码示例 ``` // 精确参数匹配 package exactmatch

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

    type Calculator interface {
        Add(a, b int) int
        Concat(s1, s2 string) string
        Multiply(factors []int) int
        UpdateUser(user *User) error
    }

    type MockCalculator struct {
        mock.Mock
    }

    func (m *MockCalculator) Add(a, b int) int {
        args := m.Called(a, b)
        return args.Int(0)
    }

    func (m *MockCalculator) Concat(s1, s2 string) string {
        args := m.Called(s1, s2)
        return args.String(0)
    }

    func (m *MockCalculator) Multiply(factors []int) int {
        args := m.Called(factors)
        return args.Int(0)
    }

    func (m *MockCalculator) UpdateUser(user *User) error {
        args := m.Called(user)
        return args.Error(0)
    }

    type User struct {
        ID   int
        Name string
    }

    func TestExactValueMatch(t *testing.T) {
        calc := new(MockCalculator)

        // 精确匹配基本类型
        calc.On("Add", 2, 3).Return(5)
        calc.On("Add", 10, 20).Return(30)

        result1 := calc.Add(2, 3)
        assert.Equal(t, 5, result1)

        result2 := calc.Add(10, 20)
        assert.Equal(t, 30, result2)

        // 不同的参数不会匹配
        // calc.Add(1, 1) // 会失败,没有匹配的期望

        calc.AssertExpectations(t)
    }

    func TestStringMatch(t *testing.T) {
        calc := new(MockCalculator)

        // 字符串精确匹配
        calc.On("Concat", "Hello", "World").Return("HelloWorld")
        calc.On("Concat", "", "").Return("")

        result1 := calc.Concat("Hello", "World")
        assert.Equal(t, "HelloWorld", result1)

        result2 := calc.Concat("", "")
        assert.Equal(t, "", result2)

        calc.AssertExpectations(t)
    }

    func TestSliceMatch(t *testing.T) {
        calc := new(MockCalculator)

        // 注意:切片按引用比较,不是按内容
        // 需要使用相同的切片实例
        factors := []int{2, 3, 4}
        calc.On("Multiply", factors).Return(24)

        result := calc.Multiply(factors)
        assert.Equal(t, 24, result)

        // 内容相同但是不同实例的切片不会匹配
        // calc.Multiply([]int{2, 3, 4}) // 会失败

        calc.AssertExpectations(t)
    }

    func TestPointerMatch(t *testing.T) {
        calc := new(MockCalculator)

        // 指针按地址匹配
        user := &User{ID: 1, Name: "Alice"}
        calc.On("UpdateUser", user).Return(nil)

        // 使用相同的指针实例
        err := calc.UpdateUser(user)
        assert.NoError(t, err)

        // 不同的指针实例即使内容相同也不匹配
        // differentUser := &User{ID: 1, Name: "Alice"}
        // calc.UpdateUser(differentUser) // 会失败

        calc.AssertExpectations(t)
    }

    func TestMatchOrdering(t *testing.T) {
        calc := new(MockCalculator)

        // 参数顺序很重要
        calc.On("Concat", "A", "B").Return("AB")
        calc.On("Concat", "B", "A").Return("BA")

        result1 := calc.Concat("A", "B")
        result2 := calc.Concat("B", "A")

        assert.Equal(t, "AB", result1)
        assert.Equal(t, "BA", result2)

        calc.AssertExpectations(t)
    }
    ```

02.模糊匹配器 a.mock.Anything mock.Anything匹配任意类型和任意值的参数。它是最宽松的匹配器,适用于不关心具体参数值的场景。可以用于任何参数位置,与其他精确参数或匹配器混合使用。 b.mock.AnythingOfType mock.AnythingOfType按类型匹配参数。传入类型的字符串表示如”string”、“int”、“*User”。这比Anything更严格,确保类型正确但不关心具体值。类型名必须准确,包括指针星号和包路径。 c.代码示例 ``` // 模糊匹配器 package fuzzymatch

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

    type DataStore interface {
        Save(key string, value interface{}) error
        Load(key string) (interface{}, error)
        Update(id int, data map[string]interface{}) error
        Query(filter func(interface{}) bool) []interface{}
    }

    type MockDataStore struct {
        mock.Mock
    }

    func (m *MockDataStore) Save(key string, value interface{}) error {
        args := m.Called(key, value)
        return args.Error(0)
    }

    func (m *MockDataStore) Load(key string) (interface{}, error) {
        args := m.Called(key)
        return args.Get(0), args.Error(1)
    }

    func (m *MockDataStore) Update(id int, data map[string]interface{}) error {
        args := m.Called(id, data)
        return args.Error(0)
    }

    func (m *MockDataStore) Query(filter func(interface{}) bool) []interface{} {
        args := m.Called(filter)
        return args.Get(0).([]interface{})
    }

    func TestMockAnything(t *testing.T) {
        store := new(MockDataStore)

        // 匹配任意参数
        store.On("Save", mock.Anything, mock.Anything).Return(nil)

        // 任何参数都会匹配
        err1 := store.Save("key1", "string value")
        err2 := store.Save("key2", 123)
        err3 := store.Save("key3", []int{1, 2, 3})

        assert.NoError(t, err1)
        assert.NoError(t, err2)
        assert.NoError(t, err3)

        store.AssertExpectations(t)
    }

    func TestMixedMatchers(t *testing.T) {
        store := new(MockDataStore)

        // 精确匹配第一个参数,模糊匹配第二个
        store.On("Save", "user:123", mock.Anything).Return(nil)

        // 第一个参数必须是"user:123",第二个可以是任意值
        err1 := store.Save("user:123", "Alice")
        err2 := store.Save("user:123", map[string]string{"name": "Alice"})

        assert.NoError(t, err1)
        assert.NoError(t, err2)

        // 第一个参数不匹配会失败
        // store.Save("user:456", "Bob") // 失败

        store.AssertExpectations(t)
    }

    func TestAnythingOfType(t *testing.T) {
        store := new(MockDataStore)

        // 按类型匹配
        store.On("Save", mock.AnythingOfType("string"), mock.AnythingOfType("string")).
            Return(nil)
        store.On("Update", mock.AnythingOfType("int"), mock.AnythingOfType("map[string]interface {}")).
            Return(nil)

        // 类型匹配成功
        err1 := store.Save("key", "value")
        assert.NoError(t, err1)

        data := map[string]interface{}{"field": "value"}
        err2 := store.Update(1, data)
        assert.NoError(t, err2)

        // 类型不匹配会失败
        // store.Save("key", 123) // 失败,第二个参数不是string

        store.AssertExpectations(t)
    }

    func TestPointerTypeMatch(t *testing.T) {
        type Record struct {
            Data string
        }

        type RecordStore interface {
            Insert(r *Record) error
            Fetch(id int) *Record
        }

        type MockRecordStore struct {
            mock.Mock
        }

        func (m *MockRecordStore) Insert(r *Record) error {
            args := m.Called(r)
            return args.Error(0)
        }

        func (m *MockRecordStore) Fetch(id int) *Record {
            args := m.Called(id)
            if args.Get(0) == nil {
                return nil
            }
            return args.Get(0).(*Record)
        }

        store := new(MockRecordStore)

        // 匹配指针类型(注意类型字符串中的*)
        store.On("Insert", mock.AnythingOfType("*fuzzymatch.Record")).Return(nil)

        record := &Record{Data: "test"}
        err := store.Insert(record)
        assert.NoError(t, err)

        store.AssertExpectations(t)
    }

    func TestComplexTypeMatch(t *testing.T) {
        store := new(MockDataStore)

        // 匹配函数类型
        store.On("Query", mock.AnythingOfType("func(interface {}) bool")).
            Return([]interface{}{1, 2, 3})

        filter := func(v interface{}) bool {
            return true
        }

        results := store.Query(filter)
        assert.Len(t, results, 3)

        store.AssertExpectations(t)
    }
    ```

03.自定义匹配器 a.MatchedBy方法 mock.MatchedBy接收一个判断函数,返回bool表示参数是否匹配。这允许实现任意复杂的匹配逻辑。函数接收interface{}类型参数,需要进行类型断言后再判断。 b.匹配器组合 多个匹配器可以组合使用,每个参数位置可以使用不同的匹配策略。可以混合精确匹配、Anything、AnythingOfType和MatchedBy。testify会从左到右依次验证每个参数。 c.代码示例 ``` // 自定义匹配器 package custommatch

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

    type Validator interface {
        ValidateEmail(email string) bool
        ValidateAge(age int) bool
        ValidateUser(user *User) error
        ProcessData(data []byte) error
    }

    type MockValidator struct {
        mock.Mock
    }

    func (m *MockValidator) ValidateEmail(email string) bool {
        args := m.Called(email)
        return args.Bool(0)
    }

    func (m *MockValidator) ValidateAge(age int) bool {
        args := m.Called(age)
        return args.Bool(0)
    }

    func (m *MockValidator) ValidateUser(user *User) error {
        args := m.Called(user)
        return args.Error(0)
    }

    func (m *MockValidator) ProcessData(data []byte) error {
        args := m.Called(data)
        return args.Error(0)
    }

    type User struct {
        Name  string
        Email string
        Age   int
    }

    func TestMatchedByString(t *testing.T) {
        validator := new(MockValidator)

        // 自定义字符串匹配:包含@符号
        validator.On("ValidateEmail",
            mock.MatchedBy(func(email interface{}) bool {
                s, ok := email.(string)
                return ok && strings.Contains(s, "@")
            })).Return(true)

        // 包含@的邮箱匹配成功
        result1 := validator.ValidateEmail("[email protected]")
        assert.True(t, result1)

        result2 := validator.ValidateEmail("[email protected]")
        assert.True(t, result2)

        // 不包含@的不匹配
        // validator.ValidateEmail("invalid") // 失败

        validator.AssertExpectations(t)
    }

    func TestMatchedByInt(t *testing.T) {
        validator := new(MockValidator)

        // 自定义整数匹配:大于等于18
        validator.On("ValidateAge",
            mock.MatchedBy(func(age interface{}) bool {
                n, ok := age.(int)
                return ok && n >= 18
            })).Return(true)

        // 成年人年龄匹配成功
        result1 := validator.ValidateAge(18)
        result2 := validator.ValidateAge(25)
        result3 := validator.ValidateAge(100)

        assert.True(t, result1)
        assert.True(t, result2)
        assert.True(t, result3)

        // 未成年不匹配
        // validator.ValidateAge(17) // 失败

        validator.AssertExpectations(t)
    }

    func TestMatchedByStruct(t *testing.T) {
        validator := new(MockValidator)

        // 自定义结构体匹配:Name不为空
        validator.On("ValidateUser",
            mock.MatchedBy(func(u interface{}) bool {
                user, ok := u.(*User)
                return ok && user != nil && user.Name != ""
            })).Return(nil)

        // 有效用户匹配成功
        err1 := validator.ValidateUser(&User{Name: "Alice"})
        err2 := validator.ValidateUser(&User{Name: "Bob", Age: 30})

        assert.NoError(t, err1)
        assert.NoError(t, err2)

        // 无效用户不匹配
        // validator.ValidateUser(&User{}) // 失败,Name为空
        // validator.ValidateUser(nil) // 失败,nil指针

        validator.AssertExpectations(t)
    }

    func TestComplexMatcher(t *testing.T) {
        validator := new(MockValidator)

        // 复杂条件:长度在10-100字节之间且包含特定前缀
        validator.On("ProcessData",
            mock.MatchedBy(func(d interface{}) bool {
                data, ok := d.([]byte)
                if !ok {
                    return false
                }
                return len(data) >= 10 &&
                       len(data) <= 100 &&
                       strings.HasPrefix(string(data), "VALID:")
            })).Return(nil)

        // 符合条件的数据
        err1 := validator.ProcessData([]byte("VALID:1234567890"))
        assert.NoError(t, err1)

        // 不符合条件
        // validator.ProcessData([]byte("short")) // 失败,太短
        // validator.ProcessData([]byte("INVALID:1234567890")) // 失败,前缀错误

        validator.AssertExpectations(t)
    }

    func TestMatcherCombination(t *testing.T) {
        type MultiParam interface {
            Process(id int, name string, data []byte) error
        }

        type MockMultiParam struct {
            mock.Mock
        }

        func (m *MockMultiParam) Process(id int, name string, data []byte) error {
            args := m.Called(id, name, data)
            return args.Error(0)
        }

        service := new(MockMultiParam)

        // 组合不同的匹配器
        service.On("Process",
            mock.MatchedBy(func(id interface{}) bool {
                n, ok := id.(int)
                return ok && n > 0 // ID必须为正数
            }),
            mock.AnythingOfType("string"), // Name必须是字符串
            mock.MatchedBy(func(data interface{}) bool {
                d, ok := data.([]byte)
                return ok && len(d) > 0 // Data不能为空
            }),
        ).Return(nil)

        // 满足所有条件
        err := service.Process(123, "test", []byte("data"))
        assert.NoError(t, err)

        service.AssertExpectations(t)
    }
    ```

04.特殊场景匹配 a.nil值匹配 nil可以作为参数直接传入On方法进行精确匹配。对于接口类型,nil表示接口值为nil。对于指针类型,nil表示空指针。使用mock.Anything也会匹配nil值。 b.空值和零值 空字符串""、空切片[]Type{}、零值0等都可以精确匹配。注意空切片和nil切片在Go中是不同的,前者是非nil但长度为0的切片,后者是nil。这在匹配时需要区分。 c.代码示例 ``` // 特殊场景参数匹配 package specialmatch

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

    type SpecialService interface {
        HandleNil(data *Data) error
        HandleEmpty(items []string) int
        HandleZero(count int) bool
        HandleInterface(value interface{}) string
    }

    type MockSpecialService struct {
        mock.Mock
    }

    func (m *MockSpecialService) HandleNil(data *Data) error {
        args := m.Called(data)
        return args.Error(0)
    }

    func (m *MockSpecialService) HandleEmpty(items []string) int {
        args := m.Called(items)
        return args.Int(0)
    }

    func (m *MockSpecialService) HandleZero(count int) bool {
        args := m.Called(count)
        return args.Bool(0)
    }

    func (m *MockSpecialService) HandleInterface(value interface{}) string {
        args := m.Called(value)
        return args.String(0)
    }

    type Data struct {
        Value string
    }

    func TestNilPointerMatch(t *testing.T) {
        service := new(MockSpecialService)

        // 精确匹配nil指针
        service.On("HandleNil", (*Data)(nil)).Return(nil)

        // 使用nil调用
        err := service.HandleNil(nil)
        assert.NoError(t, err)

        service.AssertExpectations(t)
    }

    func TestNilWithAnything(t *testing.T) {
        service := new(MockSpecialService)

        // mock.Anything也匹配nil
        service.On("HandleNil", mock.Anything).Return(nil)

        // nil匹配
        err1 := service.HandleNil(nil)
        assert.NoError(t, err1)

        // 非nil也匹配
        err2 := service.HandleNil(&Data{Value: "test"})
        assert.NoError(t, err2)

        service.AssertExpectations(t)
    }

    func TestEmptySliceMatch(t *testing.T) {
        service := new(MockSpecialService)

        // 注意:空切片和nil切片不同
        emptySlice := []string{}
        service.On("HandleEmpty", emptySlice).Return(0)

        // 使用相同的空切片实例
        count := service.HandleEmpty(emptySlice)
        assert.Equal(t, 0, count)

        service.AssertExpectations(t)
    }

    func TestNilSliceMatch(t *testing.T) {
        service := new(MockSpecialService)

        // nil切片匹配
        var nilSlice []string
        service.On("HandleEmpty", nilSlice).Return(-1)

        count := service.HandleEmpty(nilSlice)
        assert.Equal(t, -1, count)

        service.AssertExpectations(t)
    }

    func TestZeroValueMatch(t *testing.T) {
        service := new(MockSpecialService)

        // 零值精确匹配
        service.On("HandleZero", 0).Return(true)
        service.On("HandleZero", mock.MatchedBy(func(n interface{}) bool {
            num, ok := n.(int)
            return ok && num > 0
        })).Return(false)

        // 零值匹配第一个期望
        result1 := service.HandleZero(0)
        assert.True(t, result1)

        // 正数匹配第二个期望
        result2 := service.HandleZero(5)
        assert.False(t, result2)

        service.AssertExpectations(t)
    }

    func TestInterfaceNilMatch(t *testing.T) {
        service := new(MockSpecialService)

        // 接口类型的nil
        service.On("HandleInterface", nil).Return("nil value")
        service.On("HandleInterface", mock.Anything).Return("non-nil value")

        // nil匹配精确期望(精确匹配优先)
        result1 := service.HandleInterface(nil)
        assert.Equal(t, "nil value", result1)

        // 非nil匹配Anything
        result2 := service.HandleInterface("test")
        assert.Equal(t, "non-nil value", result2)

        service.AssertExpectations(t)
    }

    func TestEmptyStringMatch(t *testing.T) {
        type StringService interface {
            Process(s string) int
        }

        type MockStringService struct {
            mock.Mock
        }

        func (m *MockStringService) Process(s string) int {
            args := m.Called(s)
            return args.Int(0)
        }

        service := new(MockStringService)

        // 空字符串精确匹配
        service.On("Process", "").Return(0)
        service.On("Process", mock.Anything).Return(1)

        // 空字符串匹配精确期望
        result1 := service.Process("")
        assert.Equal(t, 0, result1)

        // 非空字符串匹配Anything
        result2 := service.Process("hello")
        assert.Equal(t, 1, result2)

        service.AssertExpectations(t)
    }
    ```

4.4 返回值控制

01.固定返回值
    a.Return方法回顾
        Return方法设置Mock方法的固定返回值。每次调用该方法都会返回相同的值。参数必须与接口方法的返回签名匹配。对于多返回值,按顺序传入所有返回值。这是最简单直接的返回值控制方式。
    b.返回nil和零值
        Go的零值如0、""、false、nil可以直接作为返回值。对于指针和error类型,nil表示空值和无错误。返回nil时要注意接收方的类型断言,避免nil pointer dereference。
    c.代码示例
        ---
        // 固定返回值控制
        package fixedreturn

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

        type ProductService interface {
            GetPrice(productID int) (float64, error)
            GetStock(productID int) (int, error)
            GetProduct(id int) (*Product, error)
            ListProducts(category string) ([]*Product, error)
        }

        type MockProductService struct {
            mock.Mock
        }

        func (m *MockProductService) GetPrice(productID int) (float64, error) {
            args := m.Called(productID)
            return args.Get(0).(float64), args.Error(1)
        }

        func (m *MockProductService) GetStock(productID int) (int, error) {
            args := m.Called(productID)
            return args.Int(0), args.Error(1)
        }

        func (m *MockProductService) GetProduct(id int) (*Product, error) {
            args := m.Called(id)
            if args.Get(0) == nil {
                return nil, args.Error(1)
            }
            return args.Get(0).(*Product), args.Error(1)
        }

        func (m *MockProductService) ListProducts(category string) ([]*Product, error) {
            args := m.Called(category)
            if args.Get(0) == nil {
                return nil, args.Error(1)
            }
            return args.Get(0).([]*Product), args.Error(1)
        }

        type Product struct {
            ID       int
            Name     string
            Price    float64
            Category string
        }

        func TestFixedReturn(t *testing.T) {
            service := new(MockProductService)

            // 固定返回基本类型
            service.On("GetPrice", 1).Return(99.99, nil)
            service.On("GetStock", 1).Return(50, nil)

            price, err := service.GetPrice(1)
            assert.NoError(t, err)
            assert.Equal(t, 99.99, price)

            stock, err := service.GetStock(1)
            assert.NoError(t, err)
            assert.Equal(t, 50, stock)

            service.AssertExpectations(t)
        }

        func TestReturnNil(t *testing.T) {
            service := new(MockProductService)

            // 返回nil对象和error
            service.On("GetProduct", 999).Return(nil, errors.New("not found"))

            product, err := service.GetProduct(999)
            assert.Error(t, err)
            assert.Nil(t, product)
            assert.Equal(t, "not found", err.Error())

            service.AssertExpectations(t)
        }

        func TestReturnStruct(t *testing.T) {
            service := new(MockProductService)

            // 返回结构体指针
            expectedProduct := &Product{
                ID:       1,
                Name:     "Laptop",
                Price:    999.99,
                Category: "Electronics",
            }
            service.On("GetProduct", 1).Return(expectedProduct, nil)

            product, err := service.GetProduct(1)
            assert.NoError(t, err)
            assert.Equal(t, expectedProduct, product)
            assert.Equal(t, "Laptop", product.Name)

            service.AssertExpectations(t)
        }

        func TestReturnSlice(t *testing.T) {
            service := new(MockProductService)

            // 返回切片
            products := []*Product{
                {ID: 1, Name: "Product1", Category: "A"},
                {ID: 2, Name: "Product2", Category: "A"},
            }
            service.On("ListProducts", "A").Return(products, nil)

            // 返回空切片
            service.On("ListProducts", "B").Return([]*Product{}, nil)

            // 返回nil切片和error
            service.On("ListProducts", "C").Return(nil, errors.New("error"))

            result1, err1 := service.ListProducts("A")
            assert.NoError(t, err1)
            assert.Len(t, result1, 2)

            result2, err2 := service.ListProducts("B")
            assert.NoError(t, err2)
            assert.Len(t, result2, 0)

            result3, err3 := service.ListProducts("C")
            assert.Error(t, err3)
            assert.Nil(t, result3)

            service.AssertExpectations(t)
        }

        func TestReturnZeroValues(t *testing.T) {
            service := new(MockProductService)

            // 返回零值
            service.On("GetPrice", 0).Return(0.0, nil)
            service.On("GetStock", 0).Return(0, nil)

            price, _ := service.GetPrice(0)
            assert.Equal(t, 0.0, price)

            stock, _ := service.GetStock(0)
            assert.Equal(t, 0, stock)

            service.AssertExpectations(t)
        }
        ---

02.动态返回值
    a.Return函数
        Return可以接收函数作为参数,实现动态计算返回值。函数的参数对应Mock方法的参数,返回值对应Mock方法的返回值。这允许根据输入参数动态生成不同的输出,适合复杂的业务逻辑模拟。
    b.Run配合Return
        Run方法在方法调用时执行副作用,可以与Return配合使用。Run先执行,然后Return返回值。Run可以用于验证参数、修改外部状态、记录调用等。两者结合实现既有副作用又有返回值的复杂场景。
    c.代码示例
        ---
        // 动态返回值控制
        package dynamicreturn

        import (
            "fmt"
            "strings"
            "testing"
            "time"
            "github.com/stretchr/testify/assert"
            "github.com/stretchr/testify/mock"
        )

        type ComputeService interface {
            Calculate(x, y int) int
            Transform(input string) (string, error)
            Generate(seed int) []int
            Timestamp() int64
        }

        type MockComputeService struct {
            mock.Mock
        }

        func (m *MockComputeService) Calculate(x, y int) int {
            args := m.Called(x, y)
            return args.Int(0)
        }

        func (m *MockComputeService) Transform(input string) (string, error) {
            args := m.Called(input)
            return args.String(0), args.Error(1)
        }

        func (m *MockComputeService) Generate(seed int) []int {
            args := m.Called(seed)
            return args.Get(0).([]int)
        }

        func (m *MockComputeService) Timestamp() int64 {
            args := m.Called()
            return args.Get(0).(int64)
        }

        func TestReturnFunction(t *testing.T) {
            service := new(MockComputeService)

            // 使用函数动态计算返回值
            service.On("Calculate", mock.Anything, mock.Anything).
                Return(func(x, y int) int {
                    return x + y
                })

            // 不同输入得到不同结果
            result1 := service.Calculate(2, 3)
            result2 := service.Calculate(10, 20)
            result3 := service.Calculate(100, 200)

            assert.Equal(t, 5, result1)
            assert.Equal(t, 30, result2)
            assert.Equal(t, 300, result3)

            service.AssertExpectations(t)
        }

        func TestReturnMultipleValues(t *testing.T) {
            service := new(MockComputeService)

            // 函数返回多个值
            service.On("Transform", mock.AnythingOfType("string")).
                Return(
                    func(input string) string {
                        return strings.ToUpper(input)
                    },
                    func(input string) error {
                        if input == "" {
                            return fmt.Errorf("empty input")
                        }
                        return nil
                    },
                )

            result1, err1 := service.Transform("hello")
            assert.NoError(t, err1)
            assert.Equal(t, "HELLO", result1)

            _, err2 := service.Transform("")
            assert.Error(t, err2)

            service.AssertExpectations(t)
        }

        func TestReturnWithSideEffect(t *testing.T) {
            service := new(MockComputeService)

            callLog := []string{}

            // Run记录调用,Return返回值
            service.On("Calculate", mock.Anything, mock.Anything).
                Run(func(args mock.Arguments) {
                    x := args.Int(0)
                    y := args.Int(1)
                    callLog = append(callLog, fmt.Sprintf("Calculate(%d, %d)", x, y))
                }).
                Return(func(x, y int) int {
                    return x * y
                })

            service.Calculate(2, 3)
            service.Calculate(4, 5)

            assert.Len(t, callLog, 2)
            assert.Equal(t, "Calculate(2, 3)", callLog[0])
            assert.Equal(t, "Calculate(4, 5)", callLog[1])

            service.AssertExpectations(t)
        }

        func TestReturnSequentialValues(t *testing.T) {
            service := new(MockComputeService)

            sequence := []int{1, 2, 3, 4, 5}
            index := 0

            // 每次调用返回序列中的下一个值
            service.On("Generate", mock.Anything).
                Return(func(seed int) []int {
                    if index >= len(sequence) {
                        return []int{}
                    }
                    result := sequence[index : index+1]
                    index++
                    return result
                })

            assert.Equal(t, []int{1}, service.Generate(0))
            assert.Equal(t, []int{2}, service.Generate(0))
            assert.Equal(t, []int{3}, service.Generate(0))

            service.AssertExpectations(t)
        }

        func TestReturnTimestamp(t *testing.T) {
            service := new(MockComputeService)

            // 每次调用返回当前时间戳
            service.On("Timestamp").
                Return(func() int64 {
                    return time.Now().Unix()
                })

            ts1 := service.Timestamp()
            time.Sleep(time.Millisecond * 100)
            ts2 := service.Timestamp()

            assert.True(t, ts2 >= ts1)

            service.AssertExpectations(t)
        }
        ---

03.条件返回
    a.基于参数的条件返回
        通过设置多个On期望,可以根据不同的参数返回不同的值。testify会按照参数匹配选择对应的期望。这是实现条件返回的最简单方式,适用于参数空间有限的场景。
    b.复杂条件逻辑
        使用Return函数可以实现任意复杂的条件逻辑。在函数内部可以使用if-else、switch等控制结构,根据参数或外部状态决定返回值。这提供了最大的灵活性。
    c.代码示例
        ---
        // 条件返回值控制
        package conditionalreturn

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

        type DiscountService interface {
            CalculateDiscount(amount float64, userType string) float64
            GetShippingCost(weight float64, distance int) (float64, error)
            ApplyPromo(code string, amount float64) (float64, error)
        }

        type MockDiscountService struct {
            mock.Mock
        }

        func (m *MockDiscountService) CalculateDiscount(amount float64, userType string) float64 {
            args := m.Called(amount, userType)
            return args.Get(0).(float64)
        }

        func (m *MockDiscountService) GetShippingCost(weight float64, distance int) (float64, error) {
            args := m.Called(weight, distance)
            return args.Get(0).(float64), args.Error(1)
        }

        func (m *MockDiscountService) ApplyPromo(code string, amount float64) (float64, error) {
            args := m.Called(code, amount)
            return args.Get(0).(float64), args.Error(1)
        }

        func TestParameterBasedReturn(t *testing.T) {
            service := new(MockDiscountService)

            // 根据用户类型返回不同折扣
            service.On("CalculateDiscount", mock.Anything, "vip").
                Return(func(amount float64, userType string) float64 {
                    return amount * 0.8 // VIP 20%折扣
                })

            service.On("CalculateDiscount", mock.Anything, "regular").
                Return(func(amount float64, userType string) float64 {
                    return amount * 0.95 // 普通用户5%折扣
                })

            service.On("CalculateDiscount", mock.Anything, "guest").
                Return(func(amount float64, userType string) float64 {
                    return amount // 访客无折扣
                })

            vipPrice := service.CalculateDiscount(100, "vip")
            regularPrice := service.CalculateDiscount(100, "regular")
            guestPrice := service.CalculateDiscount(100, "guest")

            assert.Equal(t, 80.0, vipPrice)
            assert.Equal(t, 95.0, regularPrice)
            assert.Equal(t, 100.0, guestPrice)

            service.AssertExpectations(t)
        }

        func TestComplexConditionalReturn(t *testing.T) {
            service := new(MockDiscountService)

            // 复杂的运费计算逻辑
            service.On("GetShippingCost", mock.Anything, mock.Anything).
                Return(
                    func(weight float64, distance int) float64 {
                        // 基础运费
                        base := 10.0

                        // 重量附加费
                        if weight > 5.0 {
                            base += (weight - 5.0) * 2.0
                        }

                        // 距离附加费
                        if distance > 100 {
                            base += float64(distance-100) * 0.1
                        }

                        return base
                    },
                    func(weight float64, distance int) error {
                        if weight <= 0 || distance <= 0 {
                            return errors.New("invalid parameters")
                        }
                        if weight > 100 {
                            return errors.New("weight exceeds limit")
                        }
                        return nil
                    },
                )

            // 轻量近距离
            cost1, err1 := service.GetShippingCost(3.0, 50)
            assert.NoError(t, err1)
            assert.Equal(t, 10.0, cost1)

            // 重量远距离
            cost2, err2 := service.GetShippingCost(10.0, 200)
            assert.NoError(t, err2)
            assert.Equal(t, 30.0, cost2) // 10 + (10-5)*2 + (200-100)*0.1

            // 无效参数
            _, err3 := service.GetShippingCost(0, 100)
            assert.Error(t, err3)

            // 超重
            _, err4 := service.GetShippingCost(150, 50)
            assert.Error(t, err4)

            service.AssertExpectations(t)
        }

        func TestPromoCodeReturn(t *testing.T) {
            service := new(MockDiscountService)

            // 模拟促销码验证和折扣计算
            validCodes := map[string]float64{
                "SAVE10":  0.9,
                "SAVE20":  0.8,
                "SPECIAL": 0.7,
            }

            service.On("ApplyPromo", mock.AnythingOfType("string"), mock.Anything).
                Return(
                    func(code string, amount float64) float64 {
                        if discount, ok := validCodes[code]; ok {
                            return amount * discount
                        }
                        return amount
                    },
                    func(code string, amount float64) error {
                        if _, ok := validCodes[code]; !ok {
                            return errors.New("invalid promo code")
                        }
                        if amount < 50 {
                            return errors.New("minimum amount not met")
                        }
                        return nil
                    },
                )

            // 有效促销码
            price1, err1 := service.ApplyPromo("SAVE10", 100)
            assert.NoError(t, err1)
            assert.Equal(t, 90.0, price1)

            // 无效促销码
            _, err2 := service.ApplyPromo("INVALID", 100)
            assert.Error(t, err2)

            // 金额不足
            _, err3 := service.ApplyPromo("SAVE10", 30)
            assert.Error(t, err3)

            service.AssertExpectations(t)
        }
        ---

04.返回值序列
    a.多次调用不同返回值
        有时需要同一方法在不同调用时返回不同的值。可以使用多个On调用配合Once()来实现。第一次调用匹配第一个期望,第二次匹配第二个,依此类推。这对于模拟状态变化很有用。
    b.状态机模拟
        通过闭包捕获状态变量,可以在Return函数中实现状态机。每次调用根据当前状态返回值并更新状态。这能模拟复杂的有状态服务,如连接池、会话管理等。
    c.代码示例
        ---
        // 返回值序列和状态管理
        package returnsequence

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

        type ConnectionService interface {
            Connect() error
            GetStatus() string
            Read() (string, error)
        }

        type MockConnectionService struct {
            mock.Mock
        }

        func (m *MockConnectionService) Connect() error {
            args := m.Called()
            return args.Error(0)
        }

        func (m *MockConnectionService) GetStatus() string {
            args := m.Called()
            return args.String(0)
        }

        func (m *MockConnectionService) Read() (string, error) {
            args := m.Called()
            return args.String(0), args.Error(1)
        }

        func TestSequentialReturns(t *testing.T) {
            service := new(MockConnectionService)

            // 第一次调用返回错误,第二次成功
            service.On("Connect").Return(errors.New("connection failed")).Once()
            service.On("Connect").Return(nil).Once()

            // 第一次连接失败
            err1 := service.Connect()
            assert.Error(t, err1)

            // 第二次连接成功
            err2 := service.Connect()
            assert.NoError(t, err2)

            service.AssertExpectations(t)
        }

        func TestStatusStateMachine(t *testing.T) {
            service := new(MockConnectionService)

            // 模拟连接状态转换:disconnected -> connecting -> connected
            states := []string{"disconnected", "connecting", "connected"}
            currentState := 0

            service.On("GetStatus").
                Return(func() string {
                    if currentState < len(states) {
                        state := states[currentState]
                        currentState++
                        return state
                    }
                    return states[len(states)-1]
                })

            assert.Equal(t, "disconnected", service.GetStatus())
            assert.Equal(t, "connecting", service.GetStatus())
            assert.Equal(t, "connected", service.GetStatus())
            assert.Equal(t, "connected", service.GetStatus()) // 保持在最后状态

            service.AssertExpectations(t)
        }

        func TestDataStreamSimulation(t *testing.T) {
            service := new(MockConnectionService)

            // 模拟数据流:返回一系列数据,然后EOF
            dataStream := []string{"data1", "data2", "data3"}
            index := 0

            service.On("Read").
                Return(
                    func() string {
                        if index < len(dataStream) {
                            data := dataStream[index]
                            index++
                            return data
                        }
                        return ""
                    },
                    func() error {
                        if index > len(dataStream) {
                            return errors.New("EOF")
                        }
                        return nil
                    },
                )

            // 读取数据流
            data1, err1 := service.Read()
            assert.NoError(t, err1)
            assert.Equal(t, "data1", data1)

            data2, err2 := service.Read()
            assert.NoError(t, err2)
            assert.Equal(t, "data2", data2)

            data3, err3 := service.Read()
            assert.NoError(t, err3)
            assert.Equal(t, "data3", data3)

            // EOF
            _, err4 := service.Read()
            assert.Error(t, err4)
            assert.Equal(t, "EOF", err4.Error())

            service.AssertExpectations(t)
        }

        func TestRetryPattern(t *testing.T) {
            type RetryService interface {
                TryOperation() (bool, error)
            }

            type MockRetryService struct {
                mock.Mock
            }

            func (m *MockRetryService) TryOperation() (bool, error) {
                args := m.Called()
                return args.Bool(0), args.Error(1)
            }

            service := new(MockRetryService)

            // 模拟重试:前两次失败,第三次成功
            attemptCount := 0

            service.On("TryOperation").
                Return(
                    func() bool {
                        attemptCount++
                        return attemptCount >= 3
                    },
                    func() error {
                        if attemptCount < 3 {
                            return errors.New("temporary failure")
                        }
                        return nil
                    },
                )

            // 第一次尝试
            success1, err1 := service.TryOperation()
            assert.False(t, success1)
            assert.Error(t, err1)

            // 第二次尝试
            success2, err2 := service.TryOperation()
            assert.False(t, success2)
            assert.Error(t, err2)

            // 第三次尝试成功
            success3, err3 := service.TryOperation()
            assert.True(t, success3)
            assert.NoError(t, err3)

            service.AssertExpectations(t)
        }
        ---

4.5 调用验证

01.AssertExpectations基础
    a.验证原理
        AssertExpectations验证所有通过On方法设置的期望是否都被满足。它检查方法是否被调用、调用次数是否正确、参数是否匹配。如果有未满足的期望,测试会失败并报告详细信息。这是Mock验证的核心方法。
    b.使用时机
        通常在测试结束时调用AssertExpectations。它接收*testing.T参数,用于报告失败。如果忘记调用此方法,即使Mock期望未满足,测试也可能通过,导致假阳性。建议每个使用Mock的测试都调用此方法。
    c.代码示例
        ---
        // AssertExpectations基础
        package assertbasic

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

        type NotificationService interface {
            SendEmail(to, subject, body string) error
            SendSMS(phone, message string) error
            LogEvent(event string)
        }

        type MockNotificationService struct {
            mock.Mock
        }

        func (m *MockNotificationService) SendEmail(to, subject, body string) error {
            args := m.Called(to, subject, body)
            return args.Error(0)
        }

        func (m *MockNotificationService) SendSMS(phone, message string) error {
            args := m.Called(phone, message)
            return args.Error(0)
        }

        func (m *MockNotificationService) LogEvent(event string) {
            m.Called(event)
        }

        func TestAssertExpectationsSuccess(t *testing.T) {
            service := new(MockNotificationService)

            // 设置期望
            service.On("SendEmail", "[email protected]", "Welcome", "Hello!").
                Return(nil)

            // 执行调用
            err := service.SendEmail("[email protected]", "Welcome", "Hello!")
            assert.NoError(t, err)

            // 验证期望
            service.AssertExpectations(t)
        }

        func TestAssertExpectationsMultiple(t *testing.T) {
            service := new(MockNotificationService)

            // 设置多个期望
            service.On("SendEmail", mock.Anything, mock.Anything, mock.Anything).
                Return(nil).Once()
            service.On("SendSMS", mock.Anything, mock.Anything).
                Return(nil).Once()
            service.On("LogEvent", "notification_sent").Once()

            // 执行所有调用
            service.SendEmail("[email protected]", "Test", "Body")
            service.SendSMS("1234567890", "Test SMS")
            service.LogEvent("notification_sent")

            // 所有期望都满足
            service.AssertExpectations(t)
        }

        func TestAssertExpectationsWithTimes(t *testing.T) {
            service := new(MockNotificationService)

            // 期望被调用3次
            service.On("LogEvent", "event").Times(3)

            // 调用3次
            service.LogEvent("event")
            service.LogEvent("event")
            service.LogEvent("event")

            // 验证通过
            service.AssertExpectations(t)
        }

        func TestAssertExpectationsMaybe(t *testing.T) {
            service := new(MockNotificationService)

            // Maybe期望:调用或不调用都不会失败
            service.On("SendEmail", mock.Anything, mock.Anything, mock.Anything).
                Return(nil).Maybe()

            // 不调用也能通过验证
            service.AssertExpectations(t)
        }

        func TestAssertExpectationsMaybeWithCall(t *testing.T) {
            service := new(MockNotificationService)

            service.On("LogEvent", "optional").Maybe()

            // 调用了也能通过
            service.LogEvent("optional")
            service.AssertExpectations(t)
        }
        ---

02.AssertCalled方法
    a.方法功能
        AssertCalled验证指定的方法是否被调用过,参数必须匹配。与AssertExpectations不同,它用于事后检查,不需要预先设置期望。适用于只关心方法是否被调用,不关心返回值的场景。
    b.AssertNotCalled
        AssertNotCalled验证指定的方法没有被调用。这对于测试某些代码路径不应执行特定操作很有用。例如验证在缓存命中时不应访问数据库。
    c.代码示例
        ---
        // AssertCalled和AssertNotCalled
        package assertcalled

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

        type CacheService interface {
            Get(key string) (string, bool)
            Set(key, value string)
            Delete(key string)
        }

        type MockCacheService struct {
            mock.Mock
        }

        func (m *MockCacheService) Get(key string) (string, bool) {
            args := m.Called(key)
            return args.String(0), args.Bool(1)
        }

        func (m *MockCacheService) Set(key, value string) {
            m.Called(key, value)
        }

        func (m *MockCacheService) Delete(key string) {
            m.Called(key)
        }

        type DatabaseService interface {
            Query(sql string) (string, error)
            Execute(sql string) error
        }

        type MockDatabaseService struct {
            mock.Mock
        }

        func (m *MockDatabaseService) Query(sql string) (string, error) {
            args := m.Called(sql)
            return args.String(0), args.Error(1)
        }

        func (m *MockDatabaseService) Execute(sql string) error {
            args := m.Called(sql)
            return args.Error(0)
        }

        func TestAssertCalled(t *testing.T) {
            cache := new(MockCacheService)

            // 不需要预设期望
            cache.On("Get", "key1").Return("value1", true)
            cache.On("Set", "key2", "value2").Return()

            // 执行调用
            cache.Get("key1")
            cache.Set("key2", "value2")

            // 验证方法被调用
            cache.AssertCalled(t, "Get", "key1")
            cache.AssertCalled(t, "Set", "key2", "value2")
        }

        func TestAssertNotCalled(t *testing.T) {
            cache := new(MockCacheService)
            db := new(MockDatabaseService)

            // 模拟缓存命中场景
            cache.On("Get", "user:123").Return("Alice", true)
            db.On("Query", mock.Anything).Return("Alice", nil)

            // 从缓存获取数据
            value, hit := cache.Get("user:123")
            if hit {
                // 缓存命中,不查询数据库
                _ = value
            } else {
                db.Query("SELECT name FROM users WHERE id=123")
            }

            // 验证数据库没有被调用
            db.AssertNotCalled(t, "Query", mock.Anything)

            // 验证缓存被调用
            cache.AssertCalled(t, "Get", "user:123")
        }

        func TestAssertCalledTimes(t *testing.T) {
            cache := new(MockCacheService)

            cache.On("Set", mock.Anything, mock.Anything).Return()

            // 调用3次
            cache.Set("key1", "val1")
            cache.Set("key2", "val2")
            cache.Set("key3", "val3")

            // 验证具体调用
            cache.AssertCalled(t, "Set", "key1", "val1")
            cache.AssertCalled(t, "Set", "key2", "val2")
            cache.AssertCalled(t, "Set", "key3", "val3")
        }

        func TestConditionalCalls(t *testing.T) {
            cache := new(MockCacheService)
            db := new(MockDatabaseService)

            // 设置期望
            cache.On("Get", "config").Return("", false) // 缓存未命中
            db.On("Query", "SELECT value FROM config WHERE key='config'").
                Return("config_value", nil)
            cache.On("Set", "config", "config_value").Return()

            // 先查缓存
            value, hit := cache.Get("config")
            if !hit {
                // 未命中,查数据库
                value, _ = db.Query("SELECT value FROM config WHERE key='config'")
                // 写入缓存
                cache.Set("config", value)
            }

            // 验证调用链
            cache.AssertCalled(t, "Get", "config")
            db.AssertCalled(t, "Query", "SELECT value FROM config WHERE key='config'")
            cache.AssertCalled(t, "Set", "config", "config_value")
        }
        ---

03.NumberOfCalls验证
    a.获取调用次数
        NumberOfCalls返回指定方法被调用的次数。它接收方法名和参数,返回匹配该签名的调用次数。这允许更精细的调用次数验证,不会导致测试失败,只是返回数字。
    b.AssertNumberOfCalls
        AssertNumberOfCalls断言方法被调用了指定的次数。它比On().Times()更灵活,可以在调用后验证而不需要预先设置。适用于验证循环、批处理等场景的调用次数。
    c.代码示例
        ---
        // 调用次数验证
        package callcount

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

        type LoggerService interface {
            Debug(msg string)
            Info(msg string)
            Warn(msg string)
            Error(msg string)
        }

        type MockLoggerService struct {
            mock.Mock
        }

        func (m *MockLoggerService) Debug(msg string) {
            m.Called(msg)
        }

        func (m *MockLoggerService) Info(msg string) {
            m.Called(msg)
        }

        func (m *MockLoggerService) Warn(msg string) {
            m.Called(msg)
        }

        func (m *MockLoggerService) Error(msg string) {
            m.Called(msg)
        }

        func TestNumberOfCalls(t *testing.T) {
            logger := new(MockLoggerService)

            logger.On("Info", mock.Anything).Return()
            logger.On("Error", mock.Anything).Return()

            // 执行多次调用
            logger.Info("Start")
            logger.Info("Processing")
            logger.Info("Done")
            logger.Error("Failed")

            // 获取调用次数
            infoCount := logger.NumberOfCalls("Info", mock.Anything)
            errorCount := logger.NumberOfCalls("Error", mock.Anything)

            assert.Equal(t, 3, infoCount)
            assert.Equal(t, 1, errorCount)
        }

        func TestAssertNumberOfCalls(t *testing.T) {
            logger := new(MockLoggerService)

            logger.On("Debug", mock.Anything).Return()

            // 调用5次
            for i := 0; i < 5; i++ {
                logger.Debug("debug message")
            }

            // 断言调用次数
            logger.AssertNumberOfCalls(t, "Debug", 5)
        }

        func TestBatchProcessing(t *testing.T) {
            type BatchProcessor interface {
                Process(item string) error
            }

            type MockBatchProcessor struct {
                mock.Mock
            }

            func (m *MockBatchProcessor) Process(item string) error {
                args := m.Called(item)
                return args.Error(0)
            }

            processor := new(MockBatchProcessor)
            processor.On("Process", mock.Anything).Return(nil)

            // 批处理多个项目
            items := []string{"item1", "item2", "item3", "item4", "item5"}
            for _, item := range items {
                processor.Process(item)
            }

            // 验证处理了所有项目
            count := processor.NumberOfCalls("Process", mock.Anything)
            assert.Equal(t, len(items), count)

            logger.AssertNumberOfCalls(t, "Process", 5)
        }

        func TestConditionalLogging(t *testing.T) {
            logger := new(MockLoggerService)

            logger.On("Info", mock.Anything).Return()
            logger.On("Warn", mock.Anything).Return()
            logger.On("Error", mock.Anything).Return()

            // 模拟不同级别的日志
            errors := []string{"error1", "error2"}
            warnings := []string{"warn1"}
            infos := []string{"info1", "info2", "info3", "info4"}

            for _, e := range errors {
                logger.Error(e)
            }
            for _, w := range warnings {
                logger.Warn(w)
            }
            for _, i := range infos {
                logger.Info(i)
            }

            // 验证各级别的调用次数
            logger.AssertNumberOfCalls(t, "Error", 2)
            logger.AssertNumberOfCalls(t, "Warn", 1)
            logger.AssertNumberOfCalls(t, "Info", 4)

            // 总调用次数
            totalCalls := logger.NumberOfCalls("Error", mock.Anything) +
                logger.NumberOfCalls("Warn", mock.Anything) +
                logger.NumberOfCalls("Info", mock.Anything)
            assert.Equal(t, 7, totalCalls)
        }
        ---

04.调用顺序验证
    a.顺序的重要性
        某些场景下方法调用顺序很重要,如初始化流程、事务操作、资源管理等。testify本身不直接支持顺序验证,但可以通过Run方法和外部计数器实现。验证顺序确保代码按预期的步骤执行。
    b.实现顺序验证
        使用Run方法捕获调用时间或序号,记录到外部变量。在测试结束时检查记录的顺序是否符合预期。这种方式虽然需要额外代码,但提供了完全的控制和灵活性。
    c.代码示例
        ---
        // 调用顺序验证
        package callorder

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

        type ResourceManager interface {
            Acquire() error
            Use() error
            Release() error
        }

        type MockResourceManager struct {
            mock.Mock
        }

        func (m *MockResourceManager) Acquire() error {
            args := m.Called()
            return args.Error(0)
        }

        func (m *MockResourceManager) Use() error {
            args := m.Called()
            return args.Error(0)
        }

        func (m *MockResourceManager) Release() error {
            args := m.Called()
            return args.Error(0)
        }

        func TestCallOrder(t *testing.T) {
            manager := new(MockResourceManager)

            callOrder := []string{}

            // 使用Run记录调用顺序
            manager.On("Acquire").Run(func(args mock.Arguments) {
                callOrder = append(callOrder, "Acquire")
            }).Return(nil)

            manager.On("Use").Run(func(args mock.Arguments) {
                callOrder = append(callOrder, "Use")
            }).Return(nil)

            manager.On("Release").Run(func(args mock.Arguments) {
                callOrder = append(callOrder, "Release")
            }).Return(nil)

            // 按顺序执行
            manager.Acquire()
            manager.Use()
            manager.Release()

            // 验证顺序
            expectedOrder := []string{"Acquire", "Use", "Release"}
            assert.Equal(t, expectedOrder, callOrder)

            manager.AssertExpectations(t)
        }

        func TestTransactionOrder(t *testing.T) {
            type TransactionManager interface {
                Begin() error
                Execute(sql string) error
                Commit() error
                Rollback() error
            }

            type MockTransactionManager struct {
                mock.Mock
            }

            func (m *MockTransactionManager) Begin() error {
                args := m.Called()
                return args.Error(0)
            }

            func (m *MockTransactionManager) Execute(sql string) error {
                args := m.Called(sql)
                return args.Error(0)
            }

            func (m *MockTransactionManager) Commit() error {
                args := m.Called()
                return args.Error(0)
            }

            func (m *MockTransactionManager) Rollback() error {
                args := m.Called()
                return args.Error(0)
            }

            tm := new(MockTransactionManager)

            sequence := 0
            callSequence := make(map[string]int)

            // 记录每个方法的调用序号
            tm.On("Begin").Run(func(args mock.Arguments) {
                sequence++
                callSequence["Begin"] = sequence
            }).Return(nil)

            tm.On("Execute", mock.Anything).Run(func(args mock.Arguments) {
                sequence++
                callSequence["Execute"] = sequence
            }).Return(nil)

            tm.On("Commit").Run(func(args mock.Arguments) {
                sequence++
                callSequence["Commit"] = sequence
            }).Return(nil)

            // 执行事务
            tm.Begin()
            tm.Execute("INSERT INTO users VALUES(1, 'Alice')")
            tm.Commit()

            // 验证调用顺序
            assert.Less(t, callSequence["Begin"], callSequence["Execute"])
            assert.Less(t, callSequence["Execute"], callSequence["Commit"])

            tm.AssertExpectations(t)
        }

        func TestInitializationOrder(t *testing.T) {
            type Initializer interface {
                LoadConfig() error
                ConnectDB() error
                StartServer() error
            }

            type MockInitializer struct {
                mock.Mock
            }

            func (m *MockInitializer) LoadConfig() error {
                args := m.Called()
                return args.Error(0)
            }

            func (m *MockInitializer) ConnectDB() error {
                args := m.Called()
                return args.Error(0)
            }

            func (m *MockInitializer) StartServer() error {
                args := m.Called()
                return args.Error(0)
            }

            init := new(MockInitializer)

            operations := []string{}

            init.On("LoadConfig").Run(func(args mock.Arguments) {
                operations = append(operations, "LoadConfig")
            }).Return(nil)

            init.On("ConnectDB").Run(func(args mock.Arguments) {
                // 验证LoadConfig已经被调用
                assert.Contains(t, operations, "LoadConfig",
                    "ConnectDB should be called after LoadConfig")
                operations = append(operations, "ConnectDB")
            }).Return(nil)

            init.On("StartServer").Run(func(args mock.Arguments) {
                // 验证前两步已完成
                assert.Contains(t, operations, "LoadConfig")
                assert.Contains(t, operations, "ConnectDB")
                operations = append(operations, "StartServer")
            }).Return(nil)

            // 按正确顺序初始化
            init.LoadConfig()
            init.ConnectDB()
            init.StartServer()

            // 验证最终顺序
            expected := []string{"LoadConfig", "ConnectDB", "StartServer"}
            assert.Equal(t, expected, operations)

            init.AssertExpectations(t)
        }
        ---

4.6 Mock接口

01.接口设计原则
    a.小接口优于大接口
        设计接口时应遵循接口隔离原则,创建小而专注的接口而不是大而全的接口。小接口更容易Mock,测试更清晰,依赖更明确。Go的隐式接口实现使得可以为不同测试场景定义不同的小接口。
    b.面向测试的接口
        在设计生产代码时考虑可测试性。将依赖定义为接口而不是具体类型。接口应该只包含业务逻辑需要的方法。避免依赖难以Mock的类型如全局变量、单例、静态方法等。
    c.代码示例
        ---
        // 接口设计最佳实践
        package interfacedesign

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

        // ❌ 错误:接口太大,包含过多方法
        type BadUserService interface {
            Create(user User) error
            Update(user User) error
            Delete(id int) error
            GetByID(id int) (*User, error)
            GetByEmail(email string) (*User, error)
            List(offset, limit int) ([]*User, error)
            Count() (int, error)
            Authenticate(email, password string) (bool, error)
            ResetPassword(email string) error
            SendEmail(to, subject, body string) error
            LogActivity(userID int, action string) error
        }

        // ✅ 正确:拆分成多个小接口
        type UserRepository interface {
            Create(user User) error
            Update(user User) error
            Delete(id int) error
            GetByID(id int) (*User, error)
            GetByEmail(email string) (*User, error)
        }

        type UserQueryService interface {
            List(offset, limit int) ([]*User, error)
            Count() (int, error)
        }

        type AuthService interface {
            Authenticate(email, password string) (bool, error)
            ResetPassword(email string) error
        }

        type NotificationService interface {
            SendEmail(to, subject, body string) error
        }

        type ActivityLogger interface {
            LogActivity(userID int, action string) error
        }

        // User结构体
        type User struct {
            ID    int
            Email string
            Name  string
        }

        // 业务逻辑:使用小接口组合
        type UserManager struct {
            repo   UserRepository
            auth   AuthService
            notify NotificationService
            logger ActivityLogger
        }

        func NewUserManager(repo UserRepository, auth AuthService,
            notify NotificationService, logger ActivityLogger) *UserManager {
            return &UserManager{
                repo:   repo,
                auth:   auth,
                notify: notify,
                logger: logger,
            }
        }

        func (m *UserManager) CreateUser(user User) error {
            // 创建用户
            if err := m.repo.Create(user); err != nil {
                return err
            }

            // 发送欢迎邮件
            m.notify.SendEmail(user.Email, "Welcome", "Welcome to our service!")

            // 记录活动
            m.logger.LogActivity(user.ID, "user_created")

            return nil
        }

        // Mock实现
        type MockUserRepository struct {
            mock.Mock
        }

        func (m *MockUserRepository) Create(user User) error {
            args := m.Called(user)
            return args.Error(0)
        }

        func (m *MockUserRepository) Update(user User) error {
            args := m.Called(user)
            return args.Error(0)
        }

        func (m *MockUserRepository) Delete(id int) error {
            args := m.Called(id)
            return args.Error(0)
        }

        func (m *MockUserRepository) GetByID(id int) (*User, error) {
            args := m.Called(id)
            if args.Get(0) == nil {
                return nil, args.Error(1)
            }
            return args.Get(0).(*User), args.Error(1)
        }

        func (m *MockUserRepository) GetByEmail(email string) (*User, error) {
            args := m.Called(email)
            if args.Get(0) == nil {
                return nil, args.Error(1)
            }
            return args.Get(0).(*User), args.Error(1)
        }

        type MockAuthService struct {
            mock.Mock
        }

        func (m *MockAuthService) Authenticate(email, password string) (bool, error) {
            args := m.Called(email, password)
            return args.Bool(0), args.Error(1)
        }

        func (m *MockAuthService) ResetPassword(email string) error {
            args := m.Called(email)
            return args.Error(0)
        }

        type MockNotificationService struct {
            mock.Mock
        }

        func (m *MockNotificationService) SendEmail(to, subject, body string) error {
            args := m.Called(to, subject, body)
            return args.Error(0)
        }

        type MockActivityLogger struct {
            mock.Mock
        }

        func (m *MockActivityLogger) LogActivity(userID int, action string) error {
            args := m.Called(userID, action)
            return args.Error(0)
        }

        // 测试:只需要Mock相关的接口
        func TestCreateUser(t *testing.T) {
            repo := new(MockUserRepository)
            auth := new(MockAuthService)
            notify := new(MockNotificationService)
            logger := new(MockActivityLogger)

            manager := NewUserManager(repo, auth, notify, logger)

            user := User{ID: 1, Email: "[email protected]", Name: "Test"}

            // 只设置需要的期望
            repo.On("Create", user).Return(nil)
            notify.On("SendEmail", user.Email, "Welcome", mock.Anything).Return(nil)
            logger.On("LogActivity", user.ID, "user_created").Return(nil)

            err := manager.CreateUser(user)

            assert.NoError(t, err)
            repo.AssertExpectations(t)
            notify.AssertExpectations(t)
            logger.AssertExpectations(t)
        }
        ---

02.依赖注入模式
    a.构造函数注入
        通过构造函数传入依赖接口是最推荐的方式。依赖在对象创建时就确定,不可变,线程安全。测试时可以轻松注入Mock对象。构造函数应该只接收接口类型,不接收具体实现。
    b.方法注入
        某些情况下可以通过方法参数传入依赖。这适用于依赖只在特定操作中使用的场景。方法注入比构造函数注入更灵活,但也增加了方法签名的复杂度。
    c.代码示例
        ---
        // 依赖注入模式
        package dependencyinjection

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

        // 依赖接口定义
        type Clock interface {
            Now() time.Time
        }

        type IDGenerator interface {
            NextID() int64
        }

        type Repository interface {
            Save(ctx context.Context, data interface{}) error
        }

        // 构造函数注入示例
        type OrderService struct {
            clock Clock
            idGen IDGenerator
            repo  Repository
        }

        func NewOrderService(clock Clock, idGen IDGenerator, repo Repository) *OrderService {
            return &OrderService{
                clock: clock,
                idGen: idGen,
                repo:  repo,
            }
        }

        func (s *OrderService) CreateOrder(ctx context.Context, amount float64) error {
            order := Order{
                ID:        s.idGen.NextID(),
                Amount:    amount,
                CreatedAt: s.clock.Now(),
            }
            return s.repo.Save(ctx, order)
        }

        type Order struct {
            ID        int64
            Amount    float64
            CreatedAt time.Time
        }

        // 方法注入示例
        type ReportGenerator struct{}

        func (g *ReportGenerator) Generate(ctx context.Context, fetcher DataFetcher) (*Report, error) {
            data, err := fetcher.Fetch(ctx)
            if err != nil {
                return nil, err
            }
            return &Report{Data: data}, nil
        }

        type DataFetcher interface {
            Fetch(ctx context.Context) ([]byte, error)
        }

        type Report struct {
            Data []byte
        }

        // Mock实现
        type MockClock struct {
            mock.Mock
        }

        func (m *MockClock) Now() time.Time {
            args := m.Called()
            return args.Get(0).(time.Time)
        }

        type MockIDGenerator struct {
            mock.Mock
        }

        func (m *MockIDGenerator) NextID() int64 {
            args := m.Called()
            return args.Get(0).(int64)
        }

        type MockRepository struct {
            mock.Mock
        }

        func (m *MockRepository) Save(ctx context.Context, data interface{}) error {
            args := m.Called(ctx, data)
            return args.Error(0)
        }

        type MockDataFetcher struct {
            mock.Mock
        }

        func (m *MockDataFetcher) Fetch(ctx context.Context) ([]byte, error) {
            args := m.Called(ctx)
            return args.Get(0).([]byte), args.Error(1)
        }

        // 测试构造函数注入
        func TestConstructorInjection(t *testing.T) {
            clock := new(MockClock)
            idGen := new(MockIDGenerator)
            repo := new(MockRepository)

            service := NewOrderService(clock, idGen, repo)

            ctx := context.Background()
            fixedTime := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)

            // 设置期望
            idGen.On("NextID").Return(int64(12345))
            clock.On("Now").Return(fixedTime)
            repo.On("Save", ctx, mock.MatchedBy(func(o interface{}) bool {
                order, ok := o.(Order)
                return ok && order.ID == 12345 && order.Amount == 99.99
            })).Return(nil)

            // 执行
            err := service.CreateOrder(ctx, 99.99)

            // 验证
            assert.NoError(t, err)
            clock.AssertExpectations(t)
            idGen.AssertExpectations(t)
            repo.AssertExpectations(t)
        }

        // 测试方法注入
        func TestMethodInjection(t *testing.T) {
            generator := &ReportGenerator{}
            fetcher := new(MockDataFetcher)

            ctx := context.Background()
            expectedData := []byte("report data")

            fetcher.On("Fetch", ctx).Return(expectedData, nil)

            report, err := generator.Generate(ctx, fetcher)

            assert.NoError(t, err)
            assert.Equal(t, expectedData, report.Data)
            fetcher.AssertExpectations(t)
        }

        // 工厂模式结合依赖注入
        type ServiceFactory struct {
            clock Clock
            repo  Repository
        }

        func NewServiceFactory(clock Clock, repo Repository) *ServiceFactory {
            return &ServiceFactory{clock: clock, repo: repo}
        }

        func (f *ServiceFactory) CreateOrderService(idGen IDGenerator) *OrderService {
            return NewOrderService(f.clock, idGen, f.repo)
        }

        func TestFactoryPattern(t *testing.T) {
            clock := new(MockClock)
            repo := new(MockRepository)
            idGen := new(MockIDGenerator)

            factory := NewServiceFactory(clock, repo)
            service := factory.CreateOrderService(idGen)

            ctx := context.Background()
            fixedTime := time.Now()

            clock.On("Now").Return(fixedTime)
            idGen.On("NextID").Return(int64(999))
            repo.On("Save", ctx, mock.Anything).Return(nil)

            err := service.CreateOrder(ctx, 50.0)

            assert.NoError(t, err)
        }
        ---

03.接口适配器
    a.适配器模式
        当需要Mock第三方库或标准库时,可以创建适配器接口。适配器包装原始类型,提供可测试的接口。这遵循依赖倒置原则,使代码不直接依赖具体实现。
    b.标准库封装
        对于time、os、http等标准库,可以定义接口进行封装。这使得依赖时间、文件系统、网络的代码变得可测试。适配器接口应该简洁,只暴露需要的方法。
    c.代码示例
        ---
        // 接口适配器
        package adapter

        import (
            "io"
            "os"
            "testing"
            "time"
            "github.com/stretchr/testify/assert"
            "github.com/stretchr/testify/mock"
        )

        // 时间适配器
        type TimeProvider interface {
            Now() time.Time
            Sleep(d time.Duration)
        }

        type RealTimeProvider struct{}

        func (r *RealTimeProvider) Now() time.Time {
            return time.Now()
        }

        func (r *RealTimeProvider) Sleep(d time.Duration) {
            time.Sleep(d)
        }

        // 文件系统适配器
        type FileSystem interface {
            ReadFile(path string) ([]byte, error)
            WriteFile(path string, data []byte) error
            Exists(path string) bool
        }

        type RealFileSystem struct{}

        func (f *RealFileSystem) ReadFile(path string) ([]byte, error) {
            return os.ReadFile(path)
        }

        func (f *RealFileSystem) WriteFile(path string, data []byte) error {
            return os.WriteFile(path, data, 0644)
        }

        func (f *RealFileSystem) Exists(path string) bool {
            _, err := os.Stat(path)
            return err == nil
        }

        // HTTP客户端适配器
        type HTTPClient interface {
            Get(url string) ([]byte, error)
            Post(url string, body io.Reader) ([]byte, error)
        }

        type RealHTTPClient struct {
            // 可以包装 *http.Client
        }

        func (h *RealHTTPClient) Get(url string) ([]byte, error) {
            // 实际HTTP请求
            return nil, nil
        }

        func (h *RealHTTPClient) Post(url string, body io.Reader) ([]byte, error) {
            // 实际HTTP请求
            return nil, nil
        }

        // 使用适配器的业务逻辑
        type DataSync struct {
            time TimeProvider
            fs   FileSystem
            http HTTPClient
        }

        func NewDataSync(time TimeProvider, fs FileSystem, http HTTPClient) *DataSync {
            return &DataSync{time: time, fs: fs, http: http}
        }

        func (s *DataSync) SyncData(url string, localPath string) error {
            // 检查本地文件是否存在
            if s.fs.Exists(localPath) {
                return nil
            }

            // 从远程获取数据
            data, err := s.http.Get(url)
            if err != nil {
                return err
            }

            // 写入本地
            return s.fs.WriteFile(localPath, data)
        }

        // Mock实现
        type MockTimeProvider struct {
            mock.Mock
        }

        func (m *MockTimeProvider) Now() time.Time {
            args := m.Called()
            return args.Get(0).(time.Time)
        }

        func (m *MockTimeProvider) Sleep(d time.Duration) {
            m.Called(d)
        }

        type MockFileSystem struct {
            mock.Mock
        }

        func (m *MockFileSystem) ReadFile(path string) ([]byte, error) {
            args := m.Called(path)
            return args.Get(0).([]byte), args.Error(1)
        }

        func (m *MockFileSystem) WriteFile(path string, data []byte) error {
            args := m.Called(path, data)
            return args.Error(0)
        }

        func (m *MockFileSystem) Exists(path string) bool {
            args := m.Called(path)
            return args.Bool(0)
        }

        type MockHTTPClient struct {
            mock.Mock
        }

        func (m *MockHTTPClient) Get(url string) ([]byte, error) {
            args := m.Called(url)
            return args.Get(0).([]byte), args.Error(1)
        }

        func (m *MockHTTPClient) Post(url string, body io.Reader) ([]byte, error) {
            args := m.Called(url, body)
            return args.Get(0).([]byte), args.Error(1)
        }

        // 测试
        func TestDataSync(t *testing.T) {
            timeProvider := new(MockTimeProvider)
            fs := new(MockFileSystem)
            httpClient := new(MockHTTPClient)

            sync := NewDataSync(timeProvider, fs, httpClient)

            url := "https://api.example.com/data"
            localPath := "/tmp/data.json"
            expectedData := []byte(`{"key":"value"}`)

            // 文件不存在,需要下载
            fs.On("Exists", localPath).Return(false)
            httpClient.On("Get", url).Return(expectedData, nil)
            fs.On("WriteFile", localPath, expectedData).Return(nil)

            err := sync.SyncData(url, localPath)

            assert.NoError(t, err)
            fs.AssertExpectations(t)
            httpClient.AssertExpectations(t)
        }

        func TestDataSyncFileExists(t *testing.T) {
            fs := new(MockFileSystem)
            httpClient := new(MockHTTPClient)

            sync := NewDataSync(&RealTimeProvider{}, fs, httpClient)

            localPath := "/tmp/data.json"

            // 文件已存在,不需要下载
            fs.On("Exists", localPath).Return(true)

            err := sync.SyncData("https://api.example.com/data", localPath)

            assert.NoError(t, err)
            fs.AssertExpectations(t)
            // HTTP客户端不应该被调用
            httpClient.AssertNotCalled(t, "Get", mock.Anything)
        }
        ---

04.接口组合
    a.组合多个接口
        Go支持接口组合,可以将多个小接口组合成大接口。这提供了灵活性,可以根据需要使用小接口或组合接口。测试时通常使用小接口,生产代码可以使用组合接口。
    b.条件依赖
        某些依赖可能是可选的。可以使用接口组合或者nil检查来实现可选依赖。Mock可选依赖时,可以传入nil或者使用Maybe()期望。
    c.代码示例
        ---
        // 接口组合
        package composition

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

        // 基础接口
        type Reader interface {
            Read(key string) (string, error)
        }

        type Writer interface {
            Write(key, value string) error
        }

        type Deleter interface {
            Delete(key string) error
        }

        // 组合接口
        type ReadWriter interface {
            Reader
            Writer
        }

        type Storage interface {
            Reader
            Writer
            Deleter
        }

        // 可选功能接口
        type CacheInvalidator interface {
            InvalidateCache(key string)
        }

        // 业务逻辑:只依赖需要的接口
        type DataService struct {
            storage    ReadWriter  // 必需依赖
            invalidator CacheInvalidator // 可选依赖
        }

        func NewDataService(storage ReadWriter, invalidator CacheInvalidator) *DataService {
            return &DataService{
                storage:    storage,
                invalidator: invalidator,
            }
        }

        func (s *DataService) UpdateData(key, value string) error {
            // 写入数据
            if err := s.storage.Write(key, value); err != nil {
                return err
            }

            // 如果有缓存失效器,使用它
            if s.invalidator != nil {
                s.invalidator.InvalidateCache(key)
            }

            return nil
        }

        // Mock实现
        type MockReader struct {
            mock.Mock
        }

        func (m *MockReader) Read(key string) (string, error) {
            args := m.Called(key)
            return args.String(0), args.Error(1)
        }

        type MockWriter struct {
            mock.Mock
        }

        func (m *MockWriter) Write(key, value string) error {
            args := m.Called(key, value)
            return args.Error(0)
        }

        type MockReadWriter struct {
            MockReader
            MockWriter
        }

        type MockStorage struct {
            mock.Mock
        }

        func (m *MockStorage) Read(key string) (string, error) {
            args := m.Called(key)
            return args.String(0), args.Error(1)
        }

        func (m *MockStorage) Write(key, value string) error {
            args := m.Called(key, value)
            return args.Error(0)
        }

        func (m *MockStorage) Delete(key string) error {
            args := m.Called(key)
            return args.Error(0)
        }

        type MockCacheInvalidator struct {
            mock.Mock
        }

        func (m *MockCacheInvalidator) InvalidateCache(key string) {
            m.Called(key)
        }

        // 测试:使用组合Mock
        func TestWithCacheInvalidator(t *testing.T) {
            storage := new(MockStorage)
            invalidator := new(MockCacheInvalidator)

            service := NewDataService(storage, invalidator)

            storage.On("Write", "key1", "value1").Return(nil)
            invalidator.On("InvalidateCache", "key1").Return()

            err := service.UpdateData("key1", "value1")

            assert.NoError(t, err)
            storage.AssertExpectations(t)
            invalidator.AssertExpectations(t)
        }

        // 测试:不使用缓存失效器
        func TestWithoutCacheInvalidator(t *testing.T) {
            storage := new(MockStorage)

            service := NewDataService(storage, nil)

            storage.On("Write", "key1", "value1").Return(nil)

            err := service.UpdateData("key1", "value1")

            assert.NoError(t, err)
            storage.AssertExpectations(t)
        }

        // 分离的Mock测试
        func TestSeparateMocks(t *testing.T) {
            reader := new(MockReader)
            writer := new(MockWriter)

            // 创建组合Mock
            rw := &MockReadWriter{
                MockReader: *reader,
                MockWriter: *writer,
            }

            service := NewDataService(rw, nil)

            writer.On("Write", "test", "data").Return(nil)

            err := service.UpdateData("test", "data")

            assert.NoError(t, err)
            writer.AssertExpectations(t)
        }
        ---

4.7 最佳实践

01.Mock对象命名规范
    a.命名约定
        Mock结构体应该以Mock为前缀,后接接口名。例如接口UserService的Mock命名为MockUserService。这种命名清晰表明类型的用途,便于识别和维护。避免使用FakeUserService、StubUserService等容易混淆的名称。
    b.包组织
        Mock定义可以放在测试文件中,使用_test后缀。对于被多个测试包使用的Mock,可以创建独立的mock或testing子包。不要将Mock放在生产代码包中,保持生产代码的纯净性。
    c.代码示例
        ---
        // Mock命名和组织最佳实践
        package naming

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

        // ✅ 正确:清晰的Mock命名
        type UserService interface {
            GetUser(id int) (*User, error)
            SaveUser(user *User) error
        }

        type MockUserService struct {
            mock.Mock
        }

        func (m *MockUserService) GetUser(id int) (*User, error) {
            args := m.Called(id)
            if args.Get(0) == nil {
                return nil, args.Error(1)
            }
            return args.Get(0).(*User), args.Error(1)
        }

        func (m *MockUserService) SaveUser(user *User) error {
            args := m.Called(user)
            return args.Error(0)
        }

        // ❌ 错误:命名不清晰
        type FakeUserService struct {
            mock.Mock
        }

        type StubUserService struct {
            mock.Mock
        }

        type TestUserService struct {
            mock.Mock
        }

        type User struct {
            ID   int
            Name string
        }

        // 组织方式1:Mock定义在测试文件中
        func TestUserServiceUsage(t *testing.T) {
            mockService := new(MockUserService)

            expectedUser := &User{ID: 1, Name: "Alice"}
            mockService.On("GetUser", 1).Return(expectedUser, nil)

            user, err := mockService.GetUser(1)

            assert.NoError(t, err)
            assert.Equal(t, expectedUser, user)
            mockService.AssertExpectations(t)
        }

        // 组织方式2:创建测试辅助函数
        func setupUserServiceMock(t *testing.T) *MockUserService {
            m := new(MockUserService)
            return m
        }

        func TestWithHelper(t *testing.T) {
            mockService := setupUserServiceMock(t)

            user := &User{ID: 2, Name: "Bob"}
            mockService.On("SaveUser", user).Return(nil)

            err := mockService.SaveUser(user)

            assert.NoError(t, err)
            mockService.AssertExpectations(t)
        }

        // 组织方式3:表格驱动测试中的Mock
        func TestTableDriven(t *testing.T) {
            tests := []struct {
                name        string
                userID      int
                setupMock   func(*MockUserService)
                expectError bool
            }{
                {
                    name:   "user found",
                    userID: 1,
                    setupMock: func(m *MockUserService) {
                        m.On("GetUser", 1).Return(&User{ID: 1, Name: "Alice"}, nil)
                    },
                    expectError: false,
                },
                {
                    name:   "user not found",
                    userID: 999,
                    setupMock: func(m *MockUserService) {
                        m.On("GetUser", 999).Return(nil, assert.AnError)
                    },
                    expectError: true,
                },
            }

            for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
                    mockService := new(MockUserService)
                    tt.setupMock(mockService)

                    _, err := mockService.GetUser(tt.userID)

                    if tt.expectError {
                        assert.Error(t, err)
                    } else {
                        assert.NoError(t, err)
                    }

                    mockService.AssertExpectations(t)
                })
            }
        }
        ---

02.测试隔离性
    a.每个测试独立Mock
        每个测试应该创建自己的Mock实例,不要在测试间共享Mock。共享Mock会导致状态污染,一个测试的期望影响另一个测试。子测试也应该创建独立的Mock。
    b.清理和重置
        testify的Mock不需要显式清理,每个测试创建新实例即可。避免使用全局Mock变量。如果必须使用同一个Mock运行多次,可以使用ExpectedCalls字段清空期望,但不推荐这种做法。
    c.代码示例
        ---
        // 测试隔离性最佳实践
        package isolation

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

        type Calculator interface {
            Add(a, b int) int
            Multiply(a, b int) int
        }

        type MockCalculator struct {
            mock.Mock
        }

        func (m *MockCalculator) Add(a, b int) int {
            args := m.Called(a, b)
            return args.Int(0)
        }

        func (m *MockCalculator) Multiply(a, b int) int {
            args := m.Called(a, b)
            return args.Int(0)
        }

        // ✅ 正确:每个测试独立Mock
        func TestAddition(t *testing.T) {
            calc := new(MockCalculator)
            calc.On("Add", 2, 3).Return(5)

            result := calc.Add(2, 3)
            assert.Equal(t, 5, result)
            calc.AssertExpectations(t)
        }

        func TestMultiplication(t *testing.T) {
            calc := new(MockCalculator)
            calc.On("Multiply", 2, 3).Return(6)

            result := calc.Multiply(2, 3)
            assert.Equal(t, 6, result)
            calc.AssertExpectations(t)
        }

        // ✅ 正确:子测试独立Mock
        func TestCalculatorOperations(t *testing.T) {
            t.Run("addition", func(t *testing.T) {
                calc := new(MockCalculator)
                calc.On("Add", 1, 1).Return(2)

                result := calc.Add(1, 1)
                assert.Equal(t, 2, result)
                calc.AssertExpectations(t)
            })

            t.Run("multiplication", func(t *testing.T) {
                calc := new(MockCalculator)
                calc.On("Multiply", 2, 2).Return(4)

                result := calc.Multiply(2, 2)
                assert.Equal(t, 4, result)
                calc.AssertExpectations(t)
            })
        }

        // ❌ 错误:全局共享Mock(不推荐)
        var globalCalc *MockCalculator

        func TestWithGlobalMock1(t *testing.T) {
            // 问题:全局Mock被多个测试共享
            globalCalc = new(MockCalculator)
            globalCalc.On("Add", 1, 1).Return(2)

            result := globalCalc.Add(1, 1)
            assert.Equal(t, 2, result)
        }

        func TestWithGlobalMock2(t *testing.T) {
            // 问题:可能受到前一个测试的影响
            result := globalCalc.Add(1, 1)
            assert.Equal(t, 2, result)
        }

        // ✅ 正确:使用setup函数创建独立Mock
        func setupCalculator() *MockCalculator {
            return new(MockCalculator)
        }

        func TestWithSetup1(t *testing.T) {
            calc := setupCalculator()
            calc.On("Add", 5, 5).Return(10)

            result := calc.Add(5, 5)
            assert.Equal(t, 10, result)
            calc.AssertExpectations(t)
        }

        func TestWithSetup2(t *testing.T) {
            calc := setupCalculator()
            calc.On("Multiply", 3, 3).Return(9)

            result := calc.Multiply(3, 3)
            assert.Equal(t, 9, result)
            calc.AssertExpectations(t)
        }

        // ✅ 正确:表格驱动测试,每个case独立Mock
        func TestTableDrivenWithIsolation(t *testing.T) {
            tests := []struct {
                name     string
                op       string
                a, b     int
                expected int
            }{
                {"add positive", "add", 2, 3, 5},
                {"add negative", "add", -1, -2, -3},
                {"multiply positive", "multiply", 2, 3, 6},
                {"multiply zero", "multiply", 2, 0, 0},
            }

            for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
                    // 每个case创建新Mock
                    calc := new(MockCalculator)

                    if tt.op == "add" {
                        calc.On("Add", tt.a, tt.b).Return(tt.expected)
                        result := calc.Add(tt.a, tt.b)
                        assert.Equal(t, tt.expected, result)
                    } else {
                        calc.On("Multiply", tt.a, tt.b).Return(tt.expected)
                        result := calc.Multiply(tt.a, tt.b)
                        assert.Equal(t, tt.expected, result)
                    }

                    calc.AssertExpectations(t)
                })
            }
        }
        ---

03.合理使用Mock
    a.什么时候使用Mock
        当依赖慢速外部资源如数据库、网络、文件系统时使用Mock。当依赖难以控制如随机数、时间时使用Mock。当需要模拟错误场景时使用Mock。简单的纯函数不需要Mock。
    b.什么时候不用Mock
        单元测试值对象、工具函数等无依赖的代码时不需要Mock。集成测试验证真实交互时不用Mock。过度Mock会使测试脆弱,失去对真实行为的验证。
    c.代码示例
        ---
        // 合理使用Mock的场景
        package appropriate

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

        // ✅ 场景1:需要Mock - 依赖外部服务
        type PaymentGateway interface {
            Charge(amount float64, token string) error
        }

        type MockPaymentGateway struct {
            mock.Mock
        }

        func (m *MockPaymentGateway) Charge(amount float64, token string) error {
            args := m.Called(amount, token)
            return args.Error(0)
        }

        type OrderProcessor struct {
            gateway PaymentGateway
        }

        func (p *OrderProcessor) ProcessOrder(amount float64, token string) error {
            return p.gateway.Charge(amount, token)
        }

        func TestProcessOrderWithMock(t *testing.T) {
            gateway := new(MockPaymentGateway)
            processor := &OrderProcessor{gateway: gateway}

            // Mock支付网关,避免真实支付
            gateway.On("Charge", 99.99, "token123").Return(nil)

            err := processor.ProcessOrder(99.99, "token123")

            assert.NoError(t, err)
            gateway.AssertExpectations(t)
        }

        // ✅ 场景2:需要Mock - 模拟错误场景
        func TestProcessOrderFailure(t *testing.T) {
            gateway := new(MockPaymentGateway)
            processor := &OrderProcessor{gateway: gateway}

            // Mock支付失败
            gateway.On("Charge", 99.99, "invalid_token").
                Return(errors.New("payment declined"))

            err := processor.ProcessOrder(99.99, "invalid_token")

            assert.Error(t, err)
            gateway.AssertExpectations(t)
        }

        // ✅ 场景3:需要Mock - 控制时间
        type TimeProvider interface {
            Now() time.Time
        }

        type MockTimeProvider struct {
            mock.Mock
        }

        func (m *MockTimeProvider) Now() time.Time {
            args := m.Called()
            return args.Get(0).(time.Time)
        }

        type Scheduler struct {
            time TimeProvider
        }

        func (s *Scheduler) IsExpired(expiryTime time.Time) bool {
            return s.time.Now().After(expiryTime)
        }

        func TestScheduler(t *testing.T) {
            timeMock := new(MockTimeProvider)
            scheduler := &Scheduler{time: timeMock}

            // 固定时间,便于测试
            fixedTime := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC)
            timeMock.On("Now").Return(fixedTime)

            expiryTime := time.Date(2024, 1, 1, 11, 0, 0, 0, time.UTC)

            assert.True(t, scheduler.IsExpired(expiryTime))
            timeMock.AssertExpectations(t)
        }

        // ❌ 场景4:不需要Mock - 纯函数
        func Add(a, b int) int {
            return a + b
        }

        func TestAddNoMock(t *testing.T) {
            // 直接测试,不需要Mock
            result := Add(2, 3)
            assert.Equal(t, 5, result)
        }

        // ❌ 场景5:不需要Mock - 简单值对象
        type Point struct {
            X, Y int
        }

        func (p Point) Distance(other Point) float64 {
            dx := float64(p.X - other.X)
            dy := float64(p.Y - other.Y)
            return dx*dx + dy*dy
        }

        func TestPointDistance(t *testing.T) {
            // 值对象无依赖,直接测试
            p1 := Point{X: 0, Y: 0}
            p2 := Point{X: 3, Y: 4}

            distance := p1.Distance(p2)
            assert.Equal(t, 25.0, distance)
        }

        // ⚠️ 场景6:谨慎使用Mock - 不要过度Mock
        type UserRepository interface {
            GetUser(id int) (*User, error)
        }

        type UserValidator interface {
            Validate(user *User) error
        }

        type UserFormatter interface {
            Format(user *User) string
        }

        type User struct {
            ID   int
            Name string
        }

        // 如果Formatter只是简单的字符串格式化,不需要Mock
        type SimpleFormatter struct{}

        func (f *SimpleFormatter) Format(user *User) string {
            return user.Name
        }

        func TestWithoutOverMocking(t *testing.T) {
            // Repository需要Mock(依赖数据库)
            repo := &struct {
                mock.Mock
                UserRepository
            }{}

            // Formatter不需要Mock(纯函数)
            formatter := &SimpleFormatter{}

            user := &User{ID: 1, Name: "Alice"}
            formatted := formatter.Format(user)

            assert.Equal(t, "Alice", formatted)
        }
        ---

04.Mock维护策略
    a.保持Mock简单
        Mock应该简单直接,反映接口的真实行为。避免在Mock中实现复杂业务逻辑。如果Mock变得复杂,考虑是否接口设计有问题。过于复杂的Mock可能表明接口职责过多。
    b.文档和注释
        为Mock添加必要的注释,说明模拟的场景和行为。复杂的期望设置应该有注释解释原因。这帮助其他开发者理解测试意图,便于维护。
    c.代码示例
        ---
        // Mock维护最佳实践
        package maintenance

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

        type EmailService interface {
            Send(to, subject, body string) error
            SendBatch(recipients []string, subject, body string) error
            ValidateEmail(email string) bool
        }

        type MockEmailService struct {
            mock.Mock
        }

        func (m *MockEmailService) Send(to, subject, body string) error {
            args := m.Called(to, subject, body)
            return args.Error(0)
        }

        func (m *MockEmailService) SendBatch(recipients []string, subject, body string) error {
            args := m.Called(recipients, subject, body)
            return args.Error(0)
        }

        func (m *MockEmailService) ValidateEmail(email string) bool {
            args := m.Called(email)
            return args.Bool(0)
        }

        // ✅ 良好实践:清晰的测试结构
        func TestSendWelcomeEmail(t *testing.T) {
            // Arrange - 准备测试数据和Mock
            emailService := new(MockEmailService)
            userEmail := "[email protected]"

            // Mock设置:期望发送欢迎邮件
            emailService.On("Send",
                userEmail,
                "Welcome to Our Service",
                mock.MatchedBy(func(body string) bool {
                    // 验证邮件正文包含必要信息
                    return len(body) > 0
                }),
            ).Return(nil)

            // Act - 执行测试
            err := emailService.Send(userEmail, "Welcome to Our Service", "Welcome!")

            // Assert - 验证结果
            assert.NoError(t, err)
            emailService.AssertExpectations(t)
        }

        // ✅ 良好实践:使用辅助函数简化Mock设置
        func setupEmailServiceMock() *MockEmailService {
            m := new(MockEmailService)

            // 默认行为:邮件验证
            m.On("ValidateEmail", mock.MatchedBy(func(email string) bool {
                return len(email) > 0 && len(email) < 100
            })).Return(true)

            return m
        }

        func TestWithHelper(t *testing.T) {
            emailService := setupEmailServiceMock()

            // 只需设置特定测试的期望
            emailService.On("Send", mock.Anything, mock.Anything, mock.Anything).
                Return(nil)

            err := emailService.Send("[email protected]", "Test", "Body")

            assert.NoError(t, err)
        }

        // ✅ 良好实践:测试常见错误场景
        func TestEmailServiceErrors(t *testing.T) {
            tests := []struct {
                name          string
                setupMock     func(*MockEmailService)
                email         string
                expectedError string
            }{
                {
                    name: "invalid email format",
                    setupMock: func(m *MockEmailService) {
                        m.On("Send", "invalid-email", mock.Anything, mock.Anything).
                            Return(errors.New("invalid email format"))
                    },
                    email:         "invalid-email",
                    expectedError: "invalid email format",
                },
                {
                    name: "smtp server error",
                    setupMock: func(m *MockEmailService) {
                        m.On("Send", "[email protected]", mock.Anything, mock.Anything).
                            Return(errors.New("SMTP connection failed"))
                    },
                    email:         "[email protected]",
                    expectedError: "SMTP connection failed",
                },
            }

            for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
                    emailService := new(MockEmailService)
                    tt.setupMock(emailService)

                    err := emailService.Send(tt.email, "Subject", "Body")

                    assert.Error(t, err)
                    assert.Contains(t, err.Error(), tt.expectedError)
                    emailService.AssertExpectations(t)
                })
            }
        }

        // ✅ 良好实践:注释复杂的Mock行为
        func TestComplexScenario(t *testing.T) {
            emailService := new(MockEmailService)

            recipients := []string{"[email protected]", "[email protected]", "[email protected]"}

            // 复杂场景:批量发送,模拟部分成功
            // 第一次调用失败(模拟临时网络问题)
            emailService.On("SendBatch", recipients, "Newsletter", mock.Anything).
                Return(errors.New("temporary network error")).
                Once()

            // 第二次调用成功(模拟重试成功)
            emailService.On("SendBatch", recipients, "Newsletter", mock.Anything).
                Return(nil).
                Once()

            // 第一次尝试
            err1 := emailService.SendBatch(recipients, "Newsletter", "Content")
            assert.Error(t, err1, "第一次应该失败")

            // 重试
            err2 := emailService.SendBatch(recipients, "Newsletter", "Content")
            assert.NoError(t, err2, "第二次应该成功")

            emailService.AssertExpectations(t)
        }

        // ✅ 良好实践:提取共用的Mock配置
        type testConfig struct {
            emailService *MockEmailService
        }

        func setupTestConfig() *testConfig {
            emailService := new(MockEmailService)

            // 设置通用的Mock行为
            emailService.On("ValidateEmail", mock.Anything).
                Return(func(email string) bool {
                    // 简单的邮件验证逻辑
                    return len(email) > 3
                })

            return &testConfig{
                emailService: emailService,
            }
        }

        func TestWithConfig(t *testing.T) {
            config := setupTestConfig()

            // 添加测试特定的期望
            config.emailService.On("Send", mock.Anything, mock.Anything, mock.Anything).
                Return(nil)

            err := config.emailService.Send("[email protected]", "Test", "Body")

            assert.NoError(t, err)
            config.emailService.AssertExpectations(t)
        }
        ---

5 测试套件详解

5.1 Suite基础

01.Suite概念
    a.什么是Suite
        testify的suite包提供了测试套件功能,类似于其他语言测试框架的Test Class。Suite允许将相关的测试组织在一起,共享设置和清理逻辑。通过嵌入suite.Suite类型,结构体获得了丰富的测试功能和生命周期管理能力。
    b.Suite的优势
        Suite提供了统一的测试前后处理机制,避免重复代码。支持SetupTest、TearDownTest等钩子函数,在测试生命周期的不同阶段执行操作。Suite使测试代码更有组织性,特别适合需要复杂初始化的测试场景。
    c.代码示例
        ---
        // Suite基础概念
        package suitebasic

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

        // 定义测试套件
        type BasicSuite struct {
            suite.Suite
            // 可以添加共享字段
            value int
        }

        // 测试方法:必须以Test开头
        func (s *BasicSuite) TestSimple() {
            s.Equal(1, 1)
            s.True(true)
            s.NotNil(s)
        }

        func (s *BasicSuite) TestAssertions() {
            s.Equal(5, 2+3)
            s.Contains("hello world", "world")
            s.Greater(10, 5)
        }

        func (s *BasicSuite) TestWithSharedState() {
            // 访问套件字段
            s.value = 42
            s.Equal(42, s.value)
        }

        // 运行套件:必需的入口函数
        func TestBasicSuite(t *testing.T) {
            suite.Run(t, new(BasicSuite))
        }

        // 更复杂的套件示例
        type UserServiceSuite struct {
            suite.Suite
            service *UserService
            users   []*User
        }

        type UserService struct {
            users map[int]*User
        }

        func NewUserService() *UserService {
            return &UserService{
                users: make(map[int]*User),
            }
        }

        func (s *UserService) AddUser(user *User) {
            s.users[user.ID] = user
        }

        func (s *UserService) GetUser(id int) *User {
            return s.users[id]
        }

        func (s *UserService) DeleteUser(id int) {
            delete(s.users, id)
        }

        type User struct {
            ID   int
            Name string
        }

        func (s *UserServiceSuite) TestAddUser() {
            user := &User{ID: 1, Name: "Alice"}
            s.service.AddUser(user)

            retrieved := s.service.GetUser(1)
            s.NotNil(retrieved)
            s.Equal("Alice", retrieved.Name)
        }

        func (s *UserServiceSuite) TestGetUser() {
            user := &User{ID: 2, Name: "Bob"}
            s.service.AddUser(user)

            result := s.service.GetUser(2)
            s.Equal(user, result)
        }

        func (s *UserServiceSuite) TestDeleteUser() {
            user := &User{ID: 3, Name: "Charlie"}
            s.service.AddUser(user)

            s.service.DeleteUser(3)

            result := s.service.GetUser(3)
            s.Nil(result)
        }

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

02.Suite结构定义
    a.嵌入suite.Suite
        创建Suite的第一步是定义一个结构体并嵌入suite.Suite。这个嵌入为结构体提供了所有断言方法和Suite功能。结构体可以添加自己的字段存储测试所需的状态、依赖、配置等。
    b.添加测试字段
        在Suite结构体中可以定义字段存储测试依赖如数据库连接、Mock对象、测试数据等。这些字段在SetupSuite或SetupTest中初始化,在测试方法中使用,在TearDown方法中清理。字段让测试间共享资源变得简单。
    c.代码示例
        ---
        // Suite结构定义
        package suitestructure

        import (
            "database/sql"
            "testing"
            "github.com/stretchr/testify/suite"
        )

        // 基础Suite:只有suite.Suite
        type MinimalSuite struct {
            suite.Suite
        }

        func (s *MinimalSuite) TestExample() {
            s.True(true)
        }

        func TestMinimalSuite(t *testing.T) {
            suite.Run(t, new(MinimalSuite))
        }

        // 带共享资源的Suite
        type DatabaseSuite struct {
            suite.Suite
            db     *sql.DB
            config Config
        }

        type Config struct {
            Host string
            Port int
        }

        // 带Mock的Suite
        type ServiceSuite struct {
            suite.Suite
            mockDB    *MockDatabase
            mockCache *MockCache
            service   *Service
        }

        type MockDatabase struct {
            data map[string]string
        }

        func (m *MockDatabase) Get(key string) string {
            return m.data[key]
        }

        func (m *MockDatabase) Set(key, value string) {
            m.data[key] = value
        }

        type MockCache struct {
            items map[string]interface{}
        }

        func (m *MockCache) Get(key string) interface{} {
            return m.items[key]
        }

        func (m *MockCache) Set(key string, value interface{}) {
            m.items[key] = value
        }

        type Service struct {
            db    *MockDatabase
            cache *MockCache
        }

        func NewService(db *MockDatabase, cache *MockCache) *Service {
            return &Service{db: db, cache: cache}
        }

        func (s *Service) GetValue(key string) string {
            // 先查缓存
            if val := s.cache.Get(key); val != nil {
                return val.(string)
            }
            // 查数据库
            val := s.db.Get(key)
            // 写入缓存
            s.cache.Set(key, val)
            return val
        }

        func (s *ServiceSuite) TestGetValueFromCache() {
            // 测试会使用Suite的字段
            s.mockCache.Set("key1", "cached_value")

            result := s.service.GetValue("key1")

            s.Equal("cached_value", result)
        }

        func (s *ServiceSuite) TestGetValueFromDatabase() {
            s.mockDB.Set("key2", "db_value")

            result := s.service.GetValue("key2")

            s.Equal("db_value", result)
            // 验证已缓存
            s.Equal("db_value", s.mockCache.Get("key2"))
        }

        func TestServiceSuite(t *testing.T) {
            suite.Run(t, new(ServiceSuite))
        }

        // 带测试数据的Suite
        type DataProcessingSuite struct {
            suite.Suite
            testData    []TestData
            processor   *DataProcessor
            expectedResults map[string]interface{}
        }

        type TestData struct {
            Input    string
            Expected string
        }

        type DataProcessor struct{}

        func (p *DataProcessor) Process(input string) string {
            return input
        }

        func (s *DataProcessingSuite) TestProcessData() {
            for _, data := range s.testData {
                result := s.processor.Process(data.Input)
                s.Equal(data.Expected, result)
            }
        }

        func TestDataProcessingSuite(t *testing.T) {
            suite.Run(t, new(DataProcessingSuite))
        }

        // 嵌套组合的Suite
        type BaseSuite struct {
            suite.Suite
            commonConfig Config
        }

        type ExtendedSuite struct {
            BaseSuite
            additionalData string
        }

        func (s *ExtendedSuite) TestUsingBaseFields() {
            s.NotNil(s.commonConfig)
            s.NotEmpty(s.additionalData)
        }

        func TestExtendedSuite(t *testing.T) {
            suite.Run(t, new(ExtendedSuite))
        }
        ---

03.运行Suite
    a.suite.Run方法
        suite.Run是运行测试套件的入口。它接收*testing.T和Suite实例,自动发现所有Test开头的方法并执行。Run会正确调用所有生命周期钩子。每个Suite需要一个TestXxxSuite函数作为go test的入口点。
    b.测试方法命名
        Suite中的测试方法必须以Test开头才会被执行。方法签名是func (s *SuiteType) TestXxx(),没有参数,没有返回值。可以有任意数量的测试方法。非Test开头的方法不会被执行,可以用作辅助函数。
    c.代码示例
        ---
        // 运行Suite详解
        package suiterun

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

        type ExampleSuite struct {
            suite.Suite
            executionOrder []string
        }

        // ✅ 会被执行:以Test开头
        func (s *ExampleSuite) TestFirst() {
            s.executionOrder = append(s.executionOrder, "TestFirst")
            s.True(true)
        }

        func (s *ExampleSuite) TestSecond() {
            s.executionOrder = append(s.executionOrder, "TestSecond")
            s.Equal(1, 1)
        }

        func (s *ExampleSuite) TestThird() {
            s.executionOrder = append(s.executionOrder, "TestThird")
            s.NotNil(s)
        }

        // ❌ 不会被执行:不以Test开头
        func (s *ExampleSuite) helperMethod() string {
            return "helper"
        }

        func (s *ExampleSuite) validateData(data string) bool {
            return len(data) > 0
        }

        // ✅ 测试可以调用辅助方法
        func (s *ExampleSuite) TestWithHelper() {
            result := s.helperMethod()
            s.Equal("helper", result)

            s.True(s.validateData("test"))
        }

        // 运行Suite的入口函数
        func TestExampleSuite(t *testing.T) {
            suite.Run(t, new(ExampleSuite))
        }

        // 多个Suite可以有各自的入口
        type AnotherSuite struct {
            suite.Suite
        }

        func (s *AnotherSuite) TestSomething() {
            s.True(true)
        }

        func TestAnotherSuite(t *testing.T) {
            suite.Run(t, new(AnotherSuite))
        }

        // 可以有多个测试入口运行同一个Suite(不常见)
        func TestExampleSuiteAlternate(t *testing.T) {
            s := new(ExampleSuite)
            suite.Run(t, s)
        }

        // Suite实例化时可以预设字段
        func TestExampleSuiteWithPreset(t *testing.T) {
            s := &ExampleSuite{
                executionOrder: []string{"preset"},
            }
            suite.Run(t, s)
        }

        // 测试方法命名示例
        type NamingExampleSuite struct {
            suite.Suite
        }

        // ✅ 正确的命名
        func (s *NamingExampleSuite) TestUserCreation() {}
        func (s *NamingExampleSuite) TestUserDeletion() {}
        func (s *NamingExampleSuite) TestDataValidation() {}
        func (s *NamingExampleSuite) Test_WithUnderscore() {}
        func (s *NamingExampleSuite) TestMultipleWords() {}

        // ❌ 这些不会被执行
        func (s *NamingExampleSuite) testLowerCase() {}      // 小写test
        func (s *NamingExampleSuite) UserTest() {}           // Test不在开头
        func (s *NamingExampleSuite) DoSomething() {}        // 没有Test
        func (s *NamingExampleSuite) setupHelper() {}        // 辅助方法

        func TestNamingExampleSuite(t *testing.T) {
            suite.Run(t, new(NamingExampleSuite))
        }

        // 验证测试执行
        type ExecutionVerificationSuite struct {
            suite.Suite
            testCount int
        }

        func (s *ExecutionVerificationSuite) TestOne() {
            s.testCount++
        }

        func (s *ExecutionVerificationSuite) TestTwo() {
            s.testCount++
        }

        func (s *ExecutionVerificationSuite) TestThree() {
            s.testCount++
        }

        func TestExecutionVerification(t *testing.T) {
            s := &ExecutionVerificationSuite{}
            suite.Run(t, s)

            // 注意:testCount在每个测试方法间不会累积
            // 因为每个测试方法执行时可能有独立的设置
        }
        ---

5.2 生命周期钩子

01.SetupSuite和TearDownSuite
    a.Suite级别的设置
        SetupSuite在整个Suite开始前执行一次,用于初始化Suite级别的资源如数据库连接、配置加载等。TearDownSuite在所有测试完成后执行一次,用于清理资源。这两个方法适合开销大且可以在测试间共享的资源。
    b.执行时机
        SetupSuite是第一个执行的方法,在任何测试方法之前。TearDownSuite是最后执行的方法,无论测试成功还是失败都会执行。如果Setup Suite失败,后续的测试方法不会执行但TearDownSuite仍会执行。
    c.代码示例
        ---
        // SetupSuite和TearDownSuite
        package suitelifecycle

        import (
            "fmt"
            "testing"
            "github.com/stretchr/testify/suite"
        )

        type LifecycleSuite struct {
            suite.Suite
            sharedResource string
            executionLog   []string
        }

        // Suite级别设置:整个Suite执行一次
        func (s *LifecycleSuite) SetupSuite() {
            s.executionLog = append(s.executionLog, "SetupSuite")
            s.sharedResource = "initialized"
            fmt.Println("SetupSuite: 初始化共享资源")
        }

        // Suite级别清理:整个Suite结束后执行一次
        func (s *LifecycleSuite) TearDownSuite() {
            s.executionLog = append(s.executionLog, "TearDownSuite")
            s.sharedResource = ""
            fmt.Println("TearDownSuite: 清理共享资源")
        }

        func (s *LifecycleSuite) TestOne() {
            s.executionLog = append(s.executionLog, "TestOne")
            s.Equal("initialized", s.sharedResource)
        }

        func (s *LifecycleSuite) TestTwo() {
            s.executionLog = append(s.executionLog, "TestTwo")
            s.NotEmpty(s.sharedResource)
        }

        func TestLifecycleSuite(t *testing.T) {
            suite.Run(t, new(LifecycleSuite))
        }

        // 实际应用:数据库连接
        type DatabaseSuite struct {
            suite.Suite
            dsn string
            // db *sql.DB // 实际项目中
        }

        func (s *DatabaseSuite) SetupSuite() {
            // 建立数据库连接(昂贵操作,只做一次)
            s.dsn = "user:pass@tcp(localhost:3306)/testdb"
            // s.db, _ = sql.Open("mysql", s.dsn)
            fmt.Println("数据库连接已建立")
        }

        func (s *DatabaseSuite) TearDownSuite() {
            // 关闭数据库连接
            // if s.db != nil {
            //     s.db.Close()
            // }
            fmt.Println("数据库连接已关闭")
        }

        func (s *DatabaseSuite) TestQuery() {
            s.NotEmpty(s.dsn)
            // 使用s.db执行查询
        }

        func (s *DatabaseSuite) TestInsert() {
            s.NotEmpty(s.dsn)
            // 使用s.db执行插入
        }

        func TestDatabaseSuite(t *testing.T) {
            suite.Run(t, new(DatabaseSuite))
        }

        // 实际应用:配置加载
        type ConfigSuite struct {
            suite.Suite
            config map[string]string
        }

        func (s *ConfigSuite) SetupSuite() {
            // 加载配置文件
            s.config = map[string]string{
                "api_url":    "https://api.example.com",
                "api_key":    "secret_key",
                "timeout":    "30",
                "max_retry":  "3",
            }
            fmt.Println("配置已加载")
        }

        func (s *ConfigSuite) TearDownSuite() {
            // 清理配置
            s.config = nil
            fmt.Println("配置已清理")
        }

        func (s *ConfigSuite) TestAPIConfiguration() {
            s.Equal("https://api.example.com", s.config["api_url"])
            s.NotEmpty(s.config["api_key"])
        }

        func (s *ConfigSuite) TestTimeoutConfiguration() {
            s.Equal("30", s.config["timeout"])
        }

        func TestConfigSuite(t *testing.T) {
            suite.Run(t, new(ConfigSuite))
        }
        ---

02.SetupTest和TearDownTest
    a.测试级别的设置
        SetupTest在每个测试方法执行前都会调用,TearDownTest在每个测试方法后都会调用。这确保每个测试都有干净的初始状态。适合初始化测试特定的数据、重置Mock、清空缓存等操作。
    b.隔离性保证
        通过SetupTest,每个测试方法都获得独立的初始状态,避免测试间相互影响。即使一个测试修改了共享数据,下一个测试也会重新初始化。TearDownTest确保测试产生的副作用被清理。
    c.代码示例
        ---
        // SetupTest和TearDownTest
        package testlifecycle

        import (
            "fmt"
            "testing"
            "github.com/stretchr/testify/suite"
        )

        type TestLevelSuite struct {
            suite.Suite
            counter      int
            testData     []string
            executionLog []string
        }

        // 每个测试前执行
        func (s *TestLevelSuite) SetupTest() {
            s.executionLog = append(s.executionLog, "SetupTest")
            s.counter = 0
            s.testData = []string{"initial"}
            fmt.Printf("SetupTest: counter=%d, data=%v\n", s.counter, s.testData)
        }

        // 每个测试后执行
        func (s *TestLevelSuite) TearDownTest() {
            s.executionLog = append(s.executionLog, "TearDownTest")
            fmt.Printf("TearDownTest: counter=%d, data=%v\n", s.counter, s.testData)
            // 清理测试数据
            s.testData = nil
        }

        func (s *TestLevelSuite) TestFirst() {
            s.executionLog = append(s.executionLog, "TestFirst")
            s.Equal(0, s.counter)
            s.Len(s.testData, 1)

            // 修改状态
            s.counter = 10
            s.testData = append(s.testData, "modified")
        }

        func (s *TestLevelSuite) TestSecond() {
            s.executionLog = append(s.executionLog, "TestSecond")
            // 状态已被SetupTest重置
            s.Equal(0, s.counter)
            s.Len(s.testData, 1)
            s.Equal("initial", s.testData[0])

            s.counter = 20
        }

        func (s *TestLevelSuite) TestThird() {
            s.executionLog = append(s.executionLog, "TestThird")
            // 再次重置
            s.Equal(0, s.counter)
            s.Len(s.testData, 1)
        }

        func TestTestLevelSuite(t *testing.T) {
            suite.Run(t, new(TestLevelSuite))
        }

        // 实际应用:Mock重置
        type MockTestSuite struct {
            suite.Suite
            mockDB    *MockDB
            mockCache *MockCache
        }

        type MockDB struct {
            data map[string]string
        }

        type MockCache struct {
            items map[string]interface{}
        }

        func (s *MockTestSuite) SetupTest() {
            // 每个测试都创建新的Mock实例
            s.mockDB = &MockDB{
                data: make(map[string]string),
            }
            s.mockCache = &MockCache{
                items: make(map[string]interface{}),
            }
            fmt.Println("SetupTest: Mock已重置")
        }

        func (s *MockTestSuite) TearDownTest() {
            // 清理Mock
            s.mockDB = nil
            s.mockCache = nil
            fmt.Println("TearDownTest: Mock已清理")
        }

        func (s *MockTestSuite) TestDBOperation() {
            // 使用干净的mockDB
            s.mockDB.data["key1"] = "value1"
            s.Equal("value1", s.mockDB.data["key1"])
        }

        func (s *MockTestSuite) TestCacheOperation() {
            // 使用干净的mockCache
            s.mockCache.items["key2"] = "value2"
            s.Equal("value2", s.mockCache.items["key2"])

            // mockDB应该是空的
            s.Empty(s.mockDB.data)
        }

        func TestMockTestSuite(t *testing.T) {
            suite.Run(t, new(MockTestSuite))
        }

        // 实际应用:临时文件管理
        type FileTestSuite struct {
            suite.Suite
            tempDir string
            files   []string
        }

        func (s *FileTestSuite) SetupTest() {
            // 创建临时目录
            s.tempDir = "/tmp/test_" + fmt.Sprint(s.T().Name())
            s.files = []string{}
            fmt.Printf("SetupTest: 临时目录创建 %s\n", s.tempDir)
        }

        func (s *FileTestSuite) TearDownTest() {
            // 清理临时文件
            for _, file := range s.files {
                fmt.Printf("TearDownTest: 删除文件 %s\n", file)
            }
            s.files = nil
            fmt.Printf("TearDownTest: 删除目录 %s\n", s.tempDir)
        }

        func (s *FileTestSuite) TestFileCreation() {
            fileName := s.tempDir + "/test.txt"
            s.files = append(s.files, fileName)
            s.NotEmpty(fileName)
        }

        func (s *FileTestSuite) TestFileReading() {
            // 每个测试都有独立的文件列表
            s.Empty(s.files)
            fileName := s.tempDir + "/data.txt"
            s.files = append(s.files, fileName)
        }

        func TestFileTestSuite(t *testing.T) {
            suite.Run(t, new(FileTestSuite))
        }
        ---

03.BeforeTest和AfterTest
    a.测试标识钩子
        BeforeTest和AfterTest接收suite名称和测试方法名作为参数,可以根据不同的测试执行不同的逻辑。它们在SetupTest之后、测试方法之前(BeforeTest)和测试方法之后、TearDownTest之前(AfterTest)执行。
    b.使用场景
        这两个钩子适合需要根据测试名称进行特殊处理的场景,如针对特定测试设置不同的配置、记录测试执行时间、设置测试特定的Mock行为等。相比SetupTest更灵活但使用频率较低。
    c.代码示例
        ---
        // BeforeTest和AfterTest
        package beforeafter

        import (
            "fmt"
            "testing"
            "time"
            "github.com/stretchr/testify/suite"
        )

        type BeforeAfterSuite struct {
            suite.Suite
            startTime    time.Time
            testConfig   map[string]string
            executionLog []string
        }

        func (s *BeforeAfterSuite) SetupTest() {
            s.executionLog = append(s.executionLog, "SetupTest")
            s.testConfig = make(map[string]string)
        }

        // BeforeTest在SetupTest之后,测试方法之前执行
        func (s *BeforeAfterSuite) BeforeTest(suiteName, testName string) {
            s.executionLog = append(s.executionLog,
                fmt.Sprintf("BeforeTest(%s, %s)", suiteName, testName))

            // 记录开始时间
            s.startTime = time.Now()

            // 根据测试名称设置不同配置
            switch testName {
            case "TestFast":
                s.testConfig["timeout"] = "1s"
            case "TestSlow":
                s.testConfig["timeout"] = "10s"
            default:
                s.testConfig["timeout"] = "5s"
            }

            fmt.Printf("BeforeTest: %s.%s 开始\n", suiteName, testName)
        }

        // AfterTest在测试方法之后,TearDownTest之前执行
        func (s *BeforeAfterSuite) AfterTest(suiteName, testName string) {
            s.executionLog = append(s.executionLog,
                fmt.Sprintf("AfterTest(%s, %s)", suiteName, testName))

            // 计算执行时间
            duration := time.Since(s.startTime)

            fmt.Printf("AfterTest: %s.%s 完成,耗时 %v\n",
                suiteName, testName, duration)
        }

        func (s *BeforeAfterSuite) TearDownTest() {
            s.executionLog = append(s.executionLog, "TearDownTest")
            s.testConfig = nil
        }

        func (s *BeforeAfterSuite) TestFast() {
            s.executionLog = append(s.executionLog, "TestFast")
            s.Equal("1s", s.testConfig["timeout"])
            time.Sleep(10 * time.Millisecond)
        }

        func (s *BeforeAfterSuite) TestSlow() {
            s.executionLog = append(s.executionLog, "TestSlow")
            s.Equal("10s", s.testConfig["timeout"])
            time.Sleep(50 * time.Millisecond)
        }

        func (s *BeforeAfterSuite) TestDefault() {
            s.executionLog = append(s.executionLog, "TestDefault")
            s.Equal("5s", s.testConfig["timeout"])
        }

        func TestBeforeAfterSuite(t *testing.T) {
            suite.Run(t, new(BeforeAfterSuite))
        }

        // 实际应用:测试分类
        type CategorySuite struct {
            suite.Suite
            category string
            metrics  map[string]int
        }

        func (s *CategorySuite) SetupSuite() {
            s.metrics = make(map[string]int)
        }

        func (s *CategorySuite) BeforeTest(suiteName, testName string) {
            // 根据测试名称确定类别
            if len(testName) > 8 && testName[:8] == "TestUnit" {
                s.category = "unit"
            } else if len(testName) > 15 && testName[:15] == "TestIntegration" {
                s.category = "integration"
            } else {
                s.category = "other"
            }

            s.metrics[s.category]++
            fmt.Printf("BeforeTest: 类别=%s\n", s.category)
        }

        func (s *CategorySuite) AfterTest(suiteName, testName string) {
            fmt.Printf("AfterTest: %s 完成(类别:%s)\n", testName, s.category)
        }

        func (s *CategorySuite) TearDownSuite() {
            fmt.Println("测试统计:")
            for category, count := range s.metrics {
                fmt.Printf("  %s: %d\n", category, count)
            }
        }

        func (s *CategorySuite) TestUnitCalculation() {
            s.Equal("unit", s.category)
        }

        func (s *CategorySuite) TestIntegrationDatabase() {
            s.Equal("integration", s.category)
        }

        func (s *CategorySuite) TestSomething() {
            s.Equal("other", s.category)
        }

        func TestCategorySuite(t *testing.T) {
            suite.Run(t, new(CategorySuite))
        }

        // 完整生命周期演示
        type FullLifecycleSuite struct {
            suite.Suite
            log []string
        }

        func (s *FullLifecycleSuite) SetupSuite() {
            s.log = append(s.log, "1.SetupSuite")
        }

        func (s *FullLifecycleSuite) SetupTest() {
            s.log = append(s.log, "2.SetupTest")
        }

        func (s *FullLifecycleSuite) BeforeTest(suiteName, testName string) {
            s.log = append(s.log, "3.BeforeTest")
        }

        func (s *FullLifecycleSuite) TestExample() {
            s.log = append(s.log, "4.TestExample")
            // 验证执行顺序
            s.Len(s.log, 4)
        }

        func (s *FullLifecycleSuite) AfterTest(suiteName, testName string) {
            s.log = append(s.log, "5.AfterTest")
        }

        func (s *FullLifecycleSuite) TearDownTest() {
            s.log = append(s.log, "6.TearDownTest")
        }

        func (s *FullLifecycleSuite) TearDownSuite() {
            s.log = append(s.log, "7.TearDownSuite")
            // 完整的执行顺序
            expected := []string{
                "1.SetupSuite",
                "2.SetupTest",
                "3.BeforeTest",
                "4.TestExample",
                "5.AfterTest",
                "6.TearDownTest",
                "7.TearDownSuite",
            }
            fmt.Printf("执行顺序: %v\n", expected)
        }

        func TestFullLifecycleSuite(t *testing.T) {
            suite.Run(t, new(FullLifecycleSuite))
        }
        ---

04.生命周期最佳实践
    a.选择合适的钩子
        SetupSuite用于昂贵的、可共享的初始化如数据库连接。SetupTest用于需要隔离的、每次测试都要重置的状态。BeforeTest用于需要根据测试名称定制的逻辑。避免在错误的钩子中执行操作导致性能问题或测试污染。
    b.错误处理
        Setup方法中的错误应该使用s.T().Fatal()或s.Fail()报告,这会终止测试。TearDown方法应该尽可能健壮,即使出错也要尝试清理资源。使用defer可以确保清理代码一定执行。
    c.代码示例
        ---
        // 生命周期最佳实践
        package lifecycle best

        import (
            "errors"
            "fmt"
            "testing"
            "github.com/stretchr/testify/suite"
        )

        type BestPracticeSuite struct {
            suite.Suite
            expensiveResource interface{}
            testResource      interface{}
        }

        // ✅ 正确:昂贵资源在SetupSuite
        func (s *BestPracticeSuite) SetupSuite() {
            var err error
            s.expensiveResource, err = initializeExpensiveResource()
            if err != nil {
                s.T().Fatalf("初始化失败: %v", err)
            }
        }

        // ✅ 正确:测试级资源在SetupTest
        func (s *BestPracticeSuite) SetupTest() {
            s.testResource = createTestResource()
        }

        // ✅ 正确:清理使用defer确保执行
        func (s *BestPracticeSuite) TearDownTest() {
            if s.testResource != nil {
                cleanupTestResource(s.testResource)
                s.testResource = nil
            }
        }

        // ✅ 正确:健壮的TearDown
        func (s *BestPracticeSuite) TearDownSuite() {
            defer func() {
                if r := recover(); r != nil {
                    fmt.Printf("TearDown panic recovered: %v\n", r)
                }
            }()

            if s.expensiveResource != nil {
                cleanupExpensiveResource(s.expensiveResource)
            }
        }

        func (s *BestPracticeSuite) TestSomething() {
            s.NotNil(s.expensiveResource)
            s.NotNil(s.testResource)
        }

        func TestBestPracticeSuite(t *testing.T) {
            suite.Run(t, new(BestPracticeSuite))
        }

        // ❌ 反面示例:错误的资源管理
        type BadPracticeSuite struct {
            suite.Suite
            sharedState int
        }

        // ❌ 错误:在SetupSuite修改会在测试间共享的状态
        func (s *BadPracticeSuite) SetupSuite() {
            s.sharedState = 0 // 所有测试共享此状态
        }

        func (s *BadPracticeSuite) TestOne() {
            s.sharedState++ // 修改共享状态
            s.Equal(1, s.sharedState)
        }

        func (s *BadPracticeSuite) TestTwo() {
            s.sharedState++ // 依赖前一个测试的状态(坏的)
            // s.Equal(1, s.sharedState) // 可能失败!
        }

        // ✅ 正确的版本
        type GoodPracticeSuite struct {
            suite.Suite
            testState int
        }

        func (s *GoodPracticeSuite) SetupTest() {
            s.testState = 0 // 每个测试独立初始化
        }

        func (s *GoodPracticeSuite) TestOne() {
            s.testState++
            s.Equal(1, s.testState)
        }

        func (s *GoodPracticeSuite) TestTwo() {
            s.testState++
            s.Equal(1, s.testState) // 总是通过
        }

        func TestGoodPracticeSuite(t *testing.T) {
            suite.Run(t, new(GoodPracticeSuite))
        }

        // 辅助函数
        func initializeExpensiveResource() (interface{}, error) {
            // 模拟昂贵的初始化
            return "expensive", nil
        }

        func cleanupExpensiveResource(resource interface{}) {
            // 清理
        }

        func createTestResource() interface{} {
            return "test"
        }

        func cleanupTestResource(resource interface{}) {
            // 清理
        }
        ---

5.3 子测试

01.Run方法创建子测试
    a.子测试概念
        Suite中可以使用s.Run()创建子测试,类似于testing包的t.Run()。子测试允许在一个测试方法内组织多个相关的测试场景。每个子测试有独立的名称,失败时可以准确定位。子测试支持并发执行和独立的setup/teardown。
    b.Run方法语法
        s.Run接收测试名称和函数。函数没有参数,在其中可以使用Suite的所有断言方法。子测试可以嵌套,创建层级结构。子测试的名称会包含父测试的名称,形成完整的测试路径。
    c.代码示例
        ---
        // 子测试基础
        package subtest

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

        type SubTestSuite struct {
            suite.Suite
        }

        func (s *SubTestSuite) TestWithSubTests() {
            // 子测试1
            s.Run("positive numbers", func() {
                s.Greater(10, 5)
                s.Greater(100, 50)
            })

            // 子测试2
            s.Run("negative numbers", func() {
                s.Less(-5, 0)
                s.Less(-100, -10)
            })

            // 子测试3
            s.Run("zero", func() {
                s.Equal(0, 0)
                s.Zero(0)
            })
        }

        func (s *SubTestSuite) TestNestedSubTests() {
            s.Run("arithmetic", func() {
                s.Run("addition", func() {
                    s.Equal(5, 2+3)
                    s.Equal(10, 7+3)
                })

                s.Run("subtraction", func() {
                    s.Equal(2, 5-3)
                    s.Equal(0, 3-3)
                })
            })

            s.Run("comparison", func() {
                s.Run("equality", func() {
                    s.Equal(1, 1)
                    s.Equal("a", "a")
                })

                s.Run("inequality", func() {
                    s.NotEqual(1, 2)
                    s.NotEqual("a", "b")
                })
            })
        }

        func TestSubTestSuite(t *testing.T) {
            suite.Run(t, new(SubTestSuite))
        }

        // 表格驱动测试with子测试
        type CalculatorSuite struct {
            suite.Suite
        }

        func (s *CalculatorSuite) TestAddition() {
            testCases := []struct {
                name     string
                a, b     int
                expected int
            }{
                {"positive", 2, 3, 5},
                {"negative", -2, -3, -5},
                {"mixed", 10, -5, 5},
                {"zero", 0, 5, 5},
            }

            for _, tc := range testCases {
                s.Run(tc.name, func() {
                    result := tc.a + tc.b
                    s.Equal(tc.expected, result)
                })
            }
        }

        func (s *CalculatorSuite) TestDivision() {
            s.Run("normal division", func() {
                s.Equal(2, 10/5)
                s.Equal(3, 9/3)
            })

            s.Run("division by zero", func() {
                // 注意:Go中整数除以0会panic
                s.Panics(func() {
                    _ = 10 / 0
                })
            })

            s.Run("float division", func() {
                s.InDelta(3.333, 10.0/3.0, 0.001)
            })
        }

        func TestCalculatorSuite(t *testing.T) {
            suite.Run(t, new(CalculatorSuite))
        }
        ---

02.子测试数据隔离
    a.子测试的独立性
        每个子测试在执行时是独立的,但它们共享父测试方法的作用域。子测试内的变量修改不影响其他子测试(如果是值类型),但共享的引用类型需要注意。可以为每个子测试创建独立的数据副本。
    b.数据准备模式
        在子测试循环中,应该将循环变量赋值给局部变量,避免闭包陷阱。对于复杂数据,可以在每个子测试开始时clone或重新创建。这确保子测试间完全隔离,不会相互干扰。
    c.代码示例
        ---
        // 子测试数据隔离
        package subisolation

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

        type IsolationSuite struct {
            suite.Suite
        }

        // ✅ 正确:值类型隔离
        func (s *IsolationSuite) TestValueTypeIsolation() {
            counter := 0

            s.Run("increment once", func() {
                counter++
                s.Equal(1, counter)
            })

            s.Run("increment again", func() {
                counter++
                // counter从上一个子测试的结果继续
                s.Equal(2, counter)
            })
        }

        // ⚠️ 注意:引用类型需要小心
        func (s *IsolationSuite) TestReferenceTypeSharing() {
            shared := []int{1, 2, 3}

            s.Run("modify slice", func() {
                shared = append(shared, 4)
                s.Len(shared, 4)
            })

            s.Run("slice is modified", func() {
                // shared仍然是修改后的版本
                s.Len(shared, 4)
            })
        }

        // ✅ 正确:为每个子测试创建独立数据
        func (s *IsolationSuite) TestWithFreshData() {
            testCases := []struct {
                name string
                data []int
            }{
                {"case1", []int{1, 2, 3}},
                {"case2", []int{4, 5, 6}},
                {"case3", []int{7, 8, 9}},
            }

            for _, tc := range testCases {
                // 创建局部变量(重要!)
                tc := tc

                s.Run(tc.name, func() {
                    // 每个子测试使用独立的数据副本
                    localData := make([]int, len(tc.data))
                    copy(localData, tc.data)

                    // 修改localData不影响其他子测试
                    localData[0] = 999
                    s.Equal(999, localData[0])
                })
            }
        }

        // ✅ 正确:避免闭包陷阱
        func (s *IsolationSuite) TestClosureTrap() {
            inputs := []int{1, 2, 3, 4, 5}

            // ❌ 错误的方式
            for i, val := range inputs {
                s.Run("wrong", func() {
                    // val和i是共享的,可能不是预期的值
                    _ = val
                    _ = i
                })
            }

            // ✅ 正确的方式
            for i, val := range inputs {
                i, val := i, val // 创建局部副本
                s.Run("correct", func() {
                    s.Less(i, len(inputs))
                    s.Equal(inputs[i], val)
                })
            }
        }

        func TestIsolationSuite(t *testing.T) {
            suite.Run(t, new(IsolationSuite))
        }

        // 实际应用:用户数据测试
        type UserTestSuite struct {
            suite.Suite
        }

        type User struct {
            ID   int
            Name string
            Age  int
        }

        func (s *UserTestSuite) TestUserValidation() {
            users := []User{
                {ID: 1, Name: "Alice", Age: 25},
                {ID: 2, Name: "Bob", Age: 30},
                {ID: 3, Name: "", Age: 20},     // invalid
                {ID: 4, Name: "David", Age: 0}, // invalid
            }

            for _, user := range users {
                user := user // 局部副本
                s.Run(user.Name, func() {
                    if user.Name == "" {
                        s.Empty(user.Name)
                    } else {
                        s.NotEmpty(user.Name)
                    }

                    if user.Age == 0 {
                        s.Zero(user.Age)
                    } else {
                        s.Positive(user.Age)
                    }
                })
            }
        }

        func TestUserTestSuite(t *testing.T) {
            suite.Run(t, new(UserTestSuite))
        }
        ---

03.子测试组织策略
    a.按场景分组
        使用子测试将相关场景组织在一起。例如成功场景、失败场景、边界条件等。每个主测试方法关注一个功能点,子测试覆盖该功能的不同方面。这使测试结构清晰,易于理解和维护。
    b.表格驱动模式
        子测试与表格驱动测试完美结合。定义测试用例结构体数组,为每个用例创建子测试。用例包含输入、期望输出、测试名称等。这种模式既简洁又强大,是Go测试的最佳实践。
    c.代码示例
        ---
        // 子测试组织策略
        package suborganization

        import (
            "errors"
            "testing"
            "github.com/stretchr/testify/suite"
        )

        type OrganizationSuite struct {
            suite.Suite
        }

        // 策略1:按场景分组
        func (s *OrganizationSuite) TestEmailValidation() {
            validator := &EmailValidator{}

            s.Run("valid emails", func() {
                s.Run("simple", func() {
                    s.True(validator.IsValid("[email protected]"))
                })

                s.Run("with subdomain", func() {
                    s.True(validator.IsValid("[email protected]"))
                })

                s.Run("with plus", func() {
                    s.True(validator.IsValid("[email protected]"))
                })
            })

            s.Run("invalid emails", func() {
                s.Run("no at symbol", func() {
                    s.False(validator.IsValid("userexample.com"))
                })

                s.Run("no domain", func() {
                    s.False(validator.IsValid("user@"))
                })

                s.Run("no username", func() {
                    s.False(validator.IsValid("@example.com"))
                })
            })

            s.Run("edge cases", func() {
                s.Run("empty string", func() {
                    s.False(validator.IsValid(""))
                })

                s.Run("whitespace", func() {
                    s.False(validator.IsValid(" "))
                })
            })
        }

        // 策略2:表格驱动
        func (s *OrganizationSuite) TestPasswordStrength() {
            checker := &PasswordChecker{}

            testCases := []struct {
                name     string
                password string
                expected string
            }{
                {"very strong", "Abcd1234!@#$", "strong"},
                {"strong", "Password123!", "strong"},
                {"medium", "password123", "medium"},
                {"weak", "password", "weak"},
                {"very weak", "12345", "weak"},
                {"empty", "", "weak"},
            }

            for _, tc := range testCases {
                tc := tc
                s.Run(tc.name, func() {
                    result := checker.CheckStrength(tc.password)
                    s.Equal(tc.expected, result)
                })
            }
        }

        // 策略3:功能分解
        func (s *OrganizationSuite) TestUserRegistration() {
            service := &UserService{}

            s.Run("input validation", func() {
                s.Run("valid input", func() {
                    err := service.ValidateInput("[email protected]", "Pass123!")
                    s.NoError(err)
                })

                s.Run("invalid email", func() {
                    err := service.ValidateInput("invalid", "Pass123!")
                    s.Error(err)
                })

                s.Run("weak password", func() {
                    err := service.ValidateInput("[email protected]", "123")
                    s.Error(err)
                })
            })

            s.Run("user creation", func() {
                s.Run("success", func() {
                    user, err := service.CreateUser("[email protected]", "Pass123!")
                    s.NoError(err)
                    s.NotNil(user)
                })

                s.Run("duplicate email", func() {
                    _, err := service.CreateUser("[email protected]", "Pass123!")
                    s.Error(err)
                })
            })

            s.Run("notification", func() {
                s.Run("welcome email sent", func() {
                    // 测试邮件发送逻辑
                    s.True(true)
                })
            })
        }

        // 策略4:错误场景覆盖
        func (s *OrganizationSuite) TestFileOperation() {
            fileOps := &FileOperations{}

            s.Run("success scenarios", func() {
                s.Run("read existing file", func() {
                    data, err := fileOps.Read("/valid/path.txt")
                    s.NoError(err)
                    s.NotNil(data)
                })

                s.Run("write to writable location", func() {
                    err := fileOps.Write("/writable/file.txt", []byte("data"))
                    s.NoError(err)
                })
            })

            s.Run("error scenarios", func() {
                s.Run("file not found", func() {
                    _, err := fileOps.Read("/nonexistent/file.txt")
                    s.Error(err)
                })

                s.Run("permission denied", func() {
                    err := fileOps.Write("/readonly/file.txt", []byte("data"))
                    s.Error(err)
                })

                s.Run("disk full", func() {
                    err := fileOps.Write("/full/file.txt", make([]byte, 1e9))
                    s.Error(err)
                })
            })
        }

        func TestOrganizationSuite(t *testing.T) {
            suite.Run(t, new(OrganizationSuite))
        }

        // 辅助类型
        type EmailValidator struct{}

        func (v *EmailValidator) IsValid(email string) bool {
            return len(email) > 3 && email[0] != '@' && email[len(email)-1] != '@'
        }

        type PasswordChecker struct{}

        func (c *PasswordChecker) CheckStrength(password string) string {
            if len(password) >= 12 {
                return "strong"
            } else if len(password) >= 8 {
                return "medium"
            }
            return "weak"
        }

        type UserService struct{}

        func (s *UserService) ValidateInput(email, password string) error {
            if len(email) < 3 {
                return errors.New("invalid email")
            }
            if len(password) < 6 {
                return errors.New("weak password")
            }
            return nil
        }

        func (s *UserService) CreateUser(email, password string) (*User, error) {
            if email == "[email protected]" {
                return nil, errors.New("duplicate email")
            }
            return &User{Name: email}, nil
        }

        type FileOperations struct{}

        func (f *FileOperations) Read(path string) ([]byte, error) {
            if path == "/nonexistent/file.txt" {
                return nil, errors.New("not found")
            }
            return []byte("data"), nil
        }

        func (f *FileOperations) Write(path string, data []byte) error {
            if path == "/readonly/file.txt" {
                return errors.New("permission denied")
            }
            if len(data) > 1e6 {
                return errors.New("disk full")
            }
            return nil
        }
        ---

5.4 并发测试

01.并发测试基础
    a.t.Parallel()使用
        在Suite的测试方法中调用s.T().Parallel()可以让该测试与其他并发测试并行执行。这可以大幅减少测试套件的总执行时间。并发测试之间必须完全独立,不能共享可变状态。注意Parallel标记的测试会等到所有非并发测试完成后才开始。
    b.并发安全性
        并发测试要求被测代码和测试代码都是并发安全的。共享的Suite字段如果在测试中被修改,会导致data race。使用go test -race可以检测并发问题。确保每个并发测试有独立的数据和资源。
    c.代码示例
        ---
        // 并发测试基础
        package parallel

        import (
            "sync"
            "testing"
            "time"
            "github.com/stretchr/testify/suite"
        )

        type ParallelSuite struct {
            suite.Suite
            // 只读字段是安全的
            config string
        }

        func (s *ParallelSuite) SetupSuite() {
            s.config = "shared config"
        }

        // 并发测试1
        func (s *ParallelSuite) TestParallel1() {
            s.T().Parallel()

            time.Sleep(100 * time.Millisecond)
            s.Equal("shared config", s.config)
        }

        // 并发测试2
        func (s *ParallelSuite) TestParallel2() {
            s.T().Parallel()

            time.Sleep(100 * time.Millisecond)
            s.NotEmpty(s.config)
        }

        // 并发测试3
        func (s *ParallelSuite) TestParallel3() {
            s.T().Parallel()

            time.Sleep(100 * time.Millisecond)
            s.True(true)
        }

        func TestParallelSuite(t *testing.T) {
            suite.Run(t, new(ParallelSuite))
        }

        // 并发安全的测试
        type ConcurrentSafeSuite struct {
            suite.Suite
        }

        func (s *ConcurrentSafeSuite) TestConcurrentOperation1() {
            s.T().Parallel()

            // 每个测试有独立的数据
            data := []int{1, 2, 3}
            sum := 0
            for _, v := range data {
                sum += v
            }
            s.Equal(6, sum)
        }

        func (s *ConcurrentSafeSuite) TestConcurrentOperation2() {
            s.T().Parallel()

            // 独立的数据
            data := map[string]int{
                "a": 1,
                "b": 2,
            }
            s.Len(data, 2)
        }

        func TestConcurrentSafeSuite(t *testing.T) {
            suite.Run(t, new(ConcurrentSafeSuite))
        }

        // ⚠️ 不安全的并发测试示例
        type UnsafeSuite struct {
            suite.Suite
            counter int // 共享可变状态
        }

        // ❌ 这会产生data race
        func (s *UnsafeSuite) TestUnsafe1() {
            s.T().Parallel()
            s.counter++ // race condition
            time.Sleep(10 * time.Millisecond)
        }

        func (s *UnsafeSuite) TestUnsafe2() {
            s.T().Parallel()
            s.counter++ // race condition
            time.Sleep(10 * time.Millisecond)
        }

        // ✅ 正确的版本:使用局部变量
        type SafeSuite struct {
            suite.Suite
        }

        func (s *SafeSuite) TestSafe1() {
            s.T().Parallel()
            counter := 0
            counter++
            s.Equal(1, counter)
        }

        func (s *SafeSuite) TestSafe2() {
            s.T().Parallel()
            counter := 0
            counter++
            s.Equal(1, counter)
        }

        func TestSafeSuite(t *testing.T) {
            suite.Run(t, new(SafeSuite))
        }
        ---

02.测试并发代码
    a.goroutine测试
        测试包含goroutine的代码需要使用WaitGroup或channel等同步机制确保goroutine完成。可以使用有缓冲channel收集goroutine的结果。设置超时避免测试hang住。使用t.Parallel可以并发运行多个这样的测试。
    b.竞态条件检测
        使用go test -race运行测试可以检测data race。testify的断言不是并发安全的,不要在多个goroutine中同时调用。如果需要在goroutine中断言,使用channel收集结果,在主goroutine中断言。
    c.代码示例
        ---
        // 测试并发代码
        package concurrent

        import (
            "sync"
            "testing"
            "time"
            "github.com/stretchr/testify/suite"
        )

        type ConcurrentCodeSuite struct {
            suite.Suite
        }

        // 测试goroutine
        func (s *ConcurrentCodeSuite) TestGoroutineExecution() {
            var wg sync.WaitGroup
            results := make([]int, 0)
            mu := sync.Mutex{}

            for i := 0; i < 10; i++ {
                wg.Add(1)
                go func(n int) {
                    defer wg.Done()
                    mu.Lock()
                    results = append(results, n*2)
                    mu.Unlock()
                }(i)
            }

            wg.Wait()
            s.Len(results, 10)
        }

        // 测试channel通信
        func (s *ConcurrentCodeSuite) TestChannelCommunication() {
            ch := make(chan int, 10)

            // 生产者
            go func() {
                for i := 0; i < 10; i++ {
                    ch <- i
                }
                close(ch)
            }()

            // 消费者
            count := 0
            for range ch {
                count++
            }

            s.Equal(10, count)
        }

        // 测试超时
        func (s *ConcurrentCodeSuite) TestWithTimeout() {
            done := make(chan bool)

            go func() {
                time.Sleep(50 * time.Millisecond)
                done <- true
            }()

            select {
            case <-done:
                s.True(true, "完成")
            case <-time.After(100 * time.Millisecond):
                s.Fail("超时")
            }
        }

        // ✅ 正确:在主goroutine中断言
        func (s *ConcurrentCodeSuite) TestAssertionInMainGoroutine() {
            results := make(chan int, 10)

            // worker goroutines
            var wg sync.WaitGroup
            for i := 0; i < 10; i++ {
                wg.Add(1)
                go func(n int) {
                    defer wg.Done()
                    results <- n * n
                }(i)
            }

            // 等待并关闭channel
            go func() {
                wg.Wait()
                close(results)
            }()

            // 在主goroutine中收集结果并断言
            sum := 0
            for result := range results {
                sum += result
            }

            s.Equal(285, sum) // 0^2+1^2+...+9^2 = 285
        }

        // 测试并发安全的结构
        type SafeCounter struct {
            mu    sync.Mutex
            count int
        }

        func (c *SafeCounter) Inc() {
            c.mu.Lock()
            c.count++
            c.mu.Unlock()
        }

        func (c *SafeCounter) Value() int {
            c.mu.Lock()
            defer c.mu.Unlock()
            return c.count
        }

        func (s *ConcurrentCodeSuite) TestSafeCounter() {
            counter := &SafeCounter{}
            var wg sync.WaitGroup

            // 并发增加计数
            for i := 0; i < 1000; i++ {
                wg.Add(1)
                go func() {
                    defer wg.Done()
                    counter.Inc()
                }()
            }

            wg.Wait()
            s.Equal(1000, counter.Value())
        }

        func TestConcurrentCodeSuite(t *testing.T) {
            suite.Run(t, new(ConcurrentCodeSuite))
        }
        ---

03.并发性能测试
    a.压力测试
        使用大量并发goroutine测试系统在高负载下的行为。验证没有data race、死锁、资源泄漏等问题。可以逐渐增加并发数,观察系统表现。结合-race标志和监控工具检测问题。
    b.吞吐量测试
        测试并发操作的吞吐量,统计单位时间内完成的操作数。使用WaitGroup和time.Since测量执行时间。比较串行和并发版本的性能差异。这类测试帮助优化并发策略。
    c.代码示例
        ---
        // 并发性能测试
        package performance

        import (
            "sync"
            "sync/atomic"
            "testing"
            "time"
            "github.com/stretchr/testify/suite"
        )

        type PerformanceSuite struct {
            suite.Suite
        }

        // 压力测试
        func (s *PerformanceSuite) TestHighConcurrency() {
            const goroutines = 1000
            const iterations = 100

            var counter int64
            var wg sync.WaitGroup

            start := time.Now()

            for i := 0; i < goroutines; i++ {
                wg.Add(1)
                go func() {
                    defer wg.Done()
                    for j := 0; j < iterations; j++ {
                        atomic.AddInt64(&counter, 1)
                    }
                }()
            }

            wg.Wait()
            duration := time.Since(start)

            expected := int64(goroutines * iterations)
            s.Equal(expected, counter)
            s.T().Logf("完成 %d 操作,耗时 %v", counter, duration)
        }

        // 吞吐量测试
        func (s *PerformanceSuite) TestThroughput() {
            const duration = 1 * time.Second
            const workers = 10

            var ops int64
            stop := make(chan bool)
            var wg sync.WaitGroup

            // 启动workers
            for i := 0; i < workers; i++ {
                wg.Add(1)
                go func() {
                    defer wg.Done()
                    for {
                        select {
                        case <-stop:
                            return
                        default:
                            // 模拟操作
                            time.Sleep(time.Microsecond)
                            atomic.AddInt64(&ops, 1)
                        }
                    }
                }()
            }

            // 运行指定时间
            time.Sleep(duration)
            close(stop)
            wg.Wait()

            s.T().Logf("吞吐量: %d ops/s", ops)
            s.Greater(ops, int64(0))
        }

        // 并发vs串行对比
        func (s *PerformanceSuite) TestConcurrentVsSerial() {
            const tasks = 100

            // 串行执行
            startSerial := time.Now()
            for i := 0; i < tasks; i++ {
                time.Sleep(time.Millisecond)
            }
            serialDuration := time.Since(startSerial)

            // 并发执行
            startConcurrent := time.Now()
            var wg sync.WaitGroup
            for i := 0; i < tasks; i++ {
                wg.Add(1)
                go func() {
                    defer wg.Done()
                    time.Sleep(time.Millisecond)
                }()
            }
            wg.Wait()
            concurrentDuration := time.Since(startConcurrent)

            s.T().Logf("串行: %v, 并发: %v", serialDuration, concurrentDuration)
            s.Less(concurrentDuration, serialDuration)
        }

        // 资源池测试
        type ResourcePool struct {
            resources chan *Resource
        }

        type Resource struct {
            ID int
        }

        func NewResourcePool(size int) *ResourcePool {
            pool := &ResourcePool{
                resources: make(chan *Resource, size),
            }
            for i := 0; i < size; i++ {
                pool.resources <- &Resource{ID: i}
            }
            return pool
        }

        func (p *ResourcePool) Acquire() *Resource {
            return <-p.resources
        }

        func (p *ResourcePool) Release(r *Resource) {
            p.resources <- r
        }

        func (s *PerformanceSuite) TestResourcePool() {
            pool := NewResourcePool(10)
            const clients = 100
            var wg sync.WaitGroup

            for i := 0; i < clients; i++ {
                wg.Add(1)
                go func() {
                    defer wg.Done()

                    // 获取资源
                    resource := pool.Acquire()
                    s.NotNil(resource)

                    // 使用资源
                    time.Sleep(10 * time.Millisecond)

                    // 释放资源
                    pool.Release(resource)
                }()
            }

            wg.Wait()
            s.Len(pool.resources, 10)
        }

        func TestPerformanceSuite(t *testing.T) {
            suite.Run(t, new(PerformanceSuite))
        }
        ---

5.5 测试夹具

01.夹具概念
    a.什么是测试夹具
        测试夹具(Fixture)是测试运行所需的固定状态和环境,包括测试数据、Mock对象、数据库连接、配置等。在Suite中,夹具通常在Setup方法中创建,在TearDown方法中清理。良好的夹具管理使测试可靠、可重复、易维护。
    b.夹具的作用
        夹具提供测试所需的已知初始状态。消除测试间的依赖,确保每个测试独立运行。减少重复代码,Setup中的初始化逻辑被所有测试共享。夹具是测试可预测性的基础。
    c.代码示例
        ---
        // 测试夹具基础
        package fixture

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

        // 基础夹具示例
        type BasicFixtureSuite struct {
            suite.Suite
            // 夹具字段
            testUser  *User
            testData  []Item
            mockDB    *MockDatabase
        }

        type User struct {
            ID   int
            Name string
        }

        type Item struct {
            Name  string
            Value int
        }

        type MockDatabase struct {
            data map[string]interface{}
        }

        // 设置夹具
        func (s *BasicFixtureSuite) SetupTest() {
            // 创建测试用户
            s.testUser = &User{
                ID:   1,
                Name: "TestUser",
            }

            // 准备测试数据
            s.testData = []Item{
                {Name: "item1", Value: 10},
                {Name: "item2", Value: 20},
                {Name: "item3", Value: 30},
            }

            // 初始化Mock
            s.mockDB = &MockDatabase{
                data: make(map[string]interface{}),
            }
        }

        // 清理夹具
        func (s *BasicFixtureSuite) TearDownTest() {
            s.testUser = nil
            s.testData = nil
            s.mockDB = nil
        }

        // 测试使用夹具
        func (s *BasicFixtureSuite) TestUserFixture() {
            s.NotNil(s.testUser)
            s.Equal("TestUser", s.testUser.Name)
        }

        func (s *BasicFixtureSuite) TestDataFixture() {
            s.Len(s.testData, 3)
            s.Equal(10, s.testData[0].Value)
        }

        func (s *BasicFixtureSuite) TestMockFixture() {
            s.mockDB.data["key"] = "value"
            s.Equal("value", s.mockDB.data["key"])
        }

        func TestBasicFixtureSuite(t *testing.T) {
            suite.Run(t, new(BasicFixtureSuite))
        }
        ---

02.共享夹具
    a.Suite级夹具
        在SetupSuite中创建的夹具被所有测试共享。适用于昂贵的初始化操作如数据库连接、大文件加载等。共享夹具必须是只读的或并发安全的,避免测试间相互影响。在TearDownSuite中清理。
    b.测试级夹具
        在SetupTest中创建的夹具为每个测试独立准备。适用于可变状态、需要隔离的资源。每个测试获得新鲜的夹具实例,修改不影响其他测试。在TearDownTest中清理。
    c.代码示例
        ---
        // 共享夹具管理
        package sharedfixture

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

        type SharedFixtureSuite struct {
            suite.Suite
            // Suite级夹具:所有测试共享
            config    map[string]string
            constants []string

            // 测试级夹具:每个测试独立
            counter int
            buffer  []byte
        }

        // Suite级初始化
        func (s *SharedFixtureSuite) SetupSuite() {
            // 只初始化一次
            s.config = map[string]string{
                "db_host":   "localhost",
                "db_port":   "3306",
                "api_key":   "secret_key",
            }

            s.constants = []string{"ONE", "TWO", "THREE"}
        }

        // 测试级初始化
        func (s *SharedFixtureSuite) SetupTest() {
            // 每个测试都重新初始化
            s.counter = 0
            s.buffer = make([]byte, 0, 1024)
        }

        func (s *SharedFixtureSuite) TearDownTest() {
            s.buffer = nil
        }

        func (s *SharedFixtureSuite) TestSharedConfig() {
            // 可以读取共享配置
            s.Equal("localhost", s.config["db_host"])
            s.Len(s.constants, 3)

            // 修改测试级夹具
            s.counter = 10
            s.buffer = append(s.buffer, 'a', 'b', 'c')
        }

        func (s *SharedFixtureSuite) TestIsolatedState() {
            // 测试级夹具已重置
            s.Equal(0, s.counter)
            s.Len(s.buffer, 0)

            // 共享夹具仍然可用
            s.NotEmpty(s.config)
        }

        func TestSharedFixtureSuite(t *testing.T) {
            suite.Run(t, new(SharedFixtureSuite))
        }

        // 实际应用:数据库夹具
        type DatabaseFixtureSuite struct {
            suite.Suite
            // 共享连接
            dbConnection *DBConnection

            // 测试独立的事务
            tx *Transaction
        }

        type DBConnection struct {
            host string
        }

        type Transaction struct {
            id int
        }

        func (s *DatabaseFixtureSuite) SetupSuite() {
            // 建立数据库连接(昂贵操作)
            s.dbConnection = &DBConnection{host: "localhost"}
        }

        func (s *DatabaseFixtureSuite) SetupTest() {
            // 每个测试开始新事务
            s.tx = &Transaction{id: 1}
        }

        func (s *DatabaseFixtureSuite) TearDownTest() {
            // 回滚事务
            s.tx = nil
        }

        func (s *DatabaseFixtureSuite) TearDownSuite() {
            // 关闭连接
            s.dbConnection = nil
        }

        func (s *DatabaseFixtureSuite) TestInsert() {
            s.NotNil(s.dbConnection)
            s.NotNil(s.tx)
            // 在事务中执行插入
        }

        func (s *DatabaseFixtureSuite) TestQuery() {
            s.NotNil(s.dbConnection)
            s.NotNil(s.tx)
            // 在事务中执行查询
        }

        func TestDatabaseFixtureSuite(t *testing.T) {
            suite.Run(t, new(DatabaseFixtureSuite))
        }
        ---

03.夹具工厂模式
    a.工厂函数
        使用工厂函数创建夹具,集中管理创建逻辑。工厂函数接收参数定制夹具。这使夹具创建灵活可复用,便于在不同测试中使用相同或相似的夹具。工厂模式也便于维护和修改夹具结构。
    b.Builder模式
        对于复杂夹具,使用Builder模式逐步构建。Builder提供流畅的API设置夹具的各个部分。最后调用Build()生成夹具。这种模式使夹具创建清晰且可定制。
    c.代码示例
        ---
        // 夹具工厂模式
        package fixturefactory

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

        type FactorySuite struct {
            suite.Suite
        }

        // 简单工厂函数
        func NewTestUser(id int, name string) *User {
            return &User{
                ID:   id,
                Name: name,
            }
        }

        func NewDefaultTestUser() *User {
            return NewTestUser(1, "DefaultUser")
        }

        func (s *FactorySuite) TestWithFactory() {
            user1 := NewTestUser(1, "Alice")
            user2 := NewTestUser(2, "Bob")
            user3 := NewDefaultTestUser()

            s.Equal("Alice", user1.Name)
            s.Equal("Bob", user2.Name)
            s.Equal("DefaultUser", user3.Name)
        }

        // Builder模式
        type OrderBuilder struct {
            order *Order
        }

        type Order struct {
            ID     int
            UserID int
            Items  []OrderItem
            Total  float64
            Status string
        }

        type OrderItem struct {
            Product string
            Qty     int
            Price   float64
        }

        func NewOrderBuilder() *OrderBuilder {
            return &OrderBuilder{
                order: &Order{
                    Items:  make([]OrderItem, 0),
                    Status: "pending",
                },
            }
        }

        func (b *OrderBuilder) WithID(id int) *OrderBuilder {
            b.order.ID = id
            return b
        }

        func (b *OrderBuilder) WithUser(userID int) *OrderBuilder {
            b.order.UserID = userID
            return b
        }

        func (b *OrderBuilder) AddItem(product string, qty int, price float64) *OrderBuilder {
            b.order.Items = append(b.order.Items, OrderItem{
                Product: product,
                Qty:     qty,
                Price:   price,
            })
            b.order.Total += float64(qty) * price
            return b
        }

        func (b *OrderBuilder) WithStatus(status string) *OrderBuilder {
            b.order.Status = status
            return b
        }

        func (b *OrderBuilder) Build() *Order {
            return b.order
        }

        func (s *FactorySuite) TestWithBuilder() {
            order := NewOrderBuilder().
                WithID(100).
                WithUser(1).
                AddItem("Product1", 2, 10.0).
                AddItem("Product2", 1, 20.0).
                WithStatus("completed").
                Build()

            s.Equal(100, order.ID)
            s.Equal(1, order.UserID)
            s.Len(order.Items, 2)
            s.Equal(40.0, order.Total)
            s.Equal("completed", order.Status)
        }

        // 预定义夹具集
        type TestData struct {
            Users  []*User
            Orders []*Order
        }

        func NewTestDataSet() *TestData {
            return &TestData{
                Users: []*User{
                    {ID: 1, Name: "Alice"},
                    {ID: 2, Name: "Bob"},
                    {ID: 3, Name: "Charlie"},
                },
                Orders: []*Order{
                    {ID: 1, UserID: 1, Total: 100.0},
                    {ID: 2, UserID: 2, Total: 200.0},
                },
            }
        }

        func (s *FactorySuite) TestWithDataSet() {
            data := NewTestDataSet()

            s.Len(data.Users, 3)
            s.Len(data.Orders, 2)
            s.Equal("Alice", data.Users[0].Name)
        }

        func TestFactorySuite(t *testing.T) {
            suite.Run(t, new(FactorySuite))
        }
        ---

04.夹具最佳实践
    a.最小化夹具
        只创建测试真正需要的夹具,避免过度准备。大而全的夹具增加测试复杂度和维护成本。根据测试需求定制夹具。使用工厂或Builder创建不同大小的夹具。
    b.夹具隔离
        确保夹具间相互独立,一个测试的夹具修改不影响其他测试。避免全局状态和单例。使用SetupTest为每个测试创建新夹具。共享夹具必须是只读或并发安全的。
    c.代码示例
        ---
        // 夹具最佳实践
        package fixturebest

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

        // ✅ 正确:最小化夹具
        type MinimalFixtureSuite struct {
            suite.Suite
        }

        func (s *MinimalFixtureSuite) TestUserName() {
            // 只创建需要的数据
            user := &User{Name: "Alice"}
            s.Equal("Alice", user.Name)
        }

        func (s *MinimalFixtureSuite) TestUserID() {
            // 这个测试只需要ID
            user := &User{ID: 1}
            s.Equal(1, user.ID)
        }

        func TestMinimalFixtureSuite(t *testing.T) {
            suite.Run(t, new(MinimalFixtureSuite))
        }

        // ❌ 错误:过度准备
        type OverpreparedSuite struct {
            suite.Suite
            complexData *ComplexData
        }

        type ComplexData struct {
            Users    []*User
            Orders   []*Order
            Products []string
            Config   map[string]string
        }

        func (s *OverpreparedSuite) SetupTest() {
            // 准备了大量数据,但每个测试可能只用一小部分
            s.complexData = &ComplexData{
                Users:    make([]*User, 100),
                Orders:   make([]*Order, 200),
                Products: make([]string, 50),
                Config:   make(map[string]string),
            }
        }

        // ✅ 正确:按需创建
        type OnDemandSuite struct {
            suite.Suite
        }

        func (s *OnDemandSuite) createTestUsers(count int) []*User {
            users := make([]*User, count)
            for i := 0; i < count; i++ {
                users[i] = &User{ID: i + 1}
            }
            return users
        }

        func (s *OnDemandSuite) TestWithFewUsers() {
            users := s.createTestUsers(3)
            s.Len(users, 3)
        }

        func (s *OnDemandSuite) TestWithManyUsers() {
            users := s.createTestUsers(100)
            s.Len(users, 100)
        }

        func TestOnDemandSuite(t *testing.T) {
            suite.Run(t, new(OnDemandSuite))
        }

        // ✅ 正确:夹具清理
        type CleanupSuite struct {
            suite.Suite
            tempFiles []string
        }

        func (s *CleanupSuite) SetupTest() {
            s.tempFiles = make([]string, 0)
        }

        func (s *CleanupSuite) TearDownTest() {
            // 清理临时文件
            for _, file := range s.tempFiles {
                // os.Remove(file)
                _ = file
            }
            s.tempFiles = nil
        }

        func (s *CleanupSuite) TestFileCreation() {
            file := "/tmp/test.txt"
            s.tempFiles = append(s.tempFiles, file)
            // 创建文件...
        }

        func TestCleanupSuite(t *testing.T) {
            suite.Run(t, new(CleanupSuite))
        }

        // ✅ 正确:夹具隔离
        type IsolatedFixtureSuite struct {
            suite.Suite
        }

        func (s *IsolatedFixtureSuite) TestOne() {
            // 每个测试创建自己的数据
            data := map[string]int{"key": 1}
            data["key"] = 10
            s.Equal(10, data["key"])
        }

        func (s *IsolatedFixtureSuite) TestTwo() {
            // 独立的数据,不受Test One影响
            data := map[string]int{"key": 1}
            s.Equal(1, data["key"])
        }

        func TestIsolatedFixtureSuite(t *testing.T) {
            suite.Run(t, new(IsolatedFixtureSuite))
        }
        ---

5.6 Suite组织策略

01.按功能模块组织
    a.模块化Suite
        为每个功能模块创建独立的Suite。每个Suite专注于测试一个明确的功能领域如用户管理、订单处理等。模块化使测试结构清晰,便于定位和维护。不同模块的Suite可以并行运行,提高测试效率。
    b.命名规范
        Suite名称应该清晰表达测试的模块或功能。使用XxxSuite命名格式,Xxx是模块或功能名。测试方法名应该描述具体的测试场景。好的命名使测试自文档化,易于理解测试意图。
    c.代码示例
        ---
        // 按功能模块组织Suite
        package organization

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

        // 用户管理Suite
        type UserManagementSuite struct {
            suite.Suite
            userService *UserService
        }

        type UserService struct{}

        func (s *UserService) CreateUser(name string) *User {
            return &User{Name: name}
        }

        func (s *UserService) DeleteUser(id int) bool {
            return true
        }

        func (s *UserManagementSuite) SetupTest() {
            s.userService = &UserService{}
        }

        func (s *UserManagementSuite) TestCreateUser() {
            user := s.userService.CreateUser("Alice")
            s.NotNil(user)
            s.Equal("Alice", user.Name)
        }

        func (s *UserManagementSuite) TestDeleteUser() {
            result := s.userService.DeleteUser(1)
            s.True(result)
        }

        func TestUserManagementSuite(t *testing.T) {
            suite.Run(t, new(UserManagementSuite))
        }

        // 订单处理Suite
        type OrderProcessingSuite struct {
            suite.Suite
            orderService *OrderService
        }

        type OrderService struct{}

        func (s *OrderService) CreateOrder(userID int) *Order {
            return &Order{UserID: userID}
        }

        func (s *OrderService) CancelOrder(orderID int) error {
            return nil
        }

        func (s *OrderProcessingSuite) SetupTest() {
            s.orderService = &OrderService{}
        }

        func (s *OrderProcessingSuite) TestCreateOrder() {
            order := s.orderService.CreateOrder(1)
            s.NotNil(order)
            s.Equal(1, order.UserID)
        }

        func (s *OrderProcessingSuite) TestCancelOrder() {
            err := s.orderService.CancelOrder(1)
            s.NoError(err)
        }

        func TestOrderProcessingSuite(t *testing.T) {
            suite.Run(t, new(OrderProcessingSuite))
        }

        // 支付处理Suite
        type PaymentProcessingSuite struct {
            suite.Suite
            paymentGateway *PaymentGateway
        }

        type PaymentGateway struct{}

        func (g *PaymentGateway) ProcessPayment(amount float64) (string, error) {
            return "tx_123", nil
        }

        func (s *PaymentProcessingSuite) SetupTest() {
            s.paymentGateway = &PaymentGateway{}
        }

        func (s *PaymentProcessingSuite) TestProcessPayment() {
            txID, err := s.paymentGateway.ProcessPayment(99.99)
            s.NoError(err)
            s.NotEmpty(txID)
        }

        func TestPaymentProcessingSuite(t *testing.T) {
            suite.Run(t, new(PaymentProcessingSuite))
        }
        ---

02.Suite继承与组合
    a.基础Suite复用
        创建包含通用设置的基础Suite,其他Suite通过嵌入复用。基础Suite可以提供通用的夹具、辅助方法、配置等。这减少代码重复,统一测试基础设施。子Suite可以覆盖基础Suite的方法定制行为。
    b.组合多个Suite
        一个测试文件可以定义多个相关的Suite,每个Suite测试不同的方面。Suite间可以共享类型定义和辅助函数。根据测试需求灵活组合,保持每个Suite专注且简洁。
    c.代码示例
        ---
        // Suite继承与组合
        package inheritance

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

        // 基础Suite:提供通用功能
        type BaseSuite struct {
            suite.Suite
            config map[string]string
        }

        func (s *BaseSuite) SetupSuite() {
            s.config = map[string]string{
                "env":    "test",
                "db_url": "localhost:3306",
            }
        }

        func (s *BaseSuite) GetConfig(key string) string {
            return s.config[key]
        }

        // 继承基础Suite的具体Suite
        type DatabaseSuite struct {
            BaseSuite
            connection string
        }

        func (s *DatabaseSuite) SetupTest() {
            s.connection = s.GetConfig("db_url")
        }

        func (s *DatabaseSuite) TestConnection() {
            s.Equal("localhost:3306", s.connection)
            s.NotEmpty(s.GetConfig("env"))
        }

        func TestDatabaseSuite(t *testing.T) {
            suite.Run(t, new(DatabaseSuite))
        }

        // 另一个继承基础Suite的Suite
        type APISuite struct {
            BaseSuite
            apiURL string
        }

        func (s *APISuite) SetupTest() {
            env := s.GetConfig("env")
            if env == "test" {
                s.apiURL = "http://test.api.example.com"
            }
        }

        func (s *APISuite) TestAPIURL() {
            s.Equal("http://test.api.example.com", s.apiURL)
        }

        func TestAPISuite(t *testing.T) {
            suite.Run(t, new(APISuite))
        }

        // 组合多个关注点
        type IntegrationSuite struct {
            suite.Suite
            dbSuite  *DatabaseSuite
            apiSuite *APISuite
        }

        func (s *IntegrationSuite) SetupSuite() {
            // 注意:这只是示例,实际中通常不这样嵌套Suite
            s.dbSuite = &DatabaseSuite{}
            s.apiSuite = &APISuite{}
        }

        func (s *IntegrationSuite) TestIntegration() {
            s.NotNil(s.dbSuite)
            s.NotNil(s.apiSuite)
        }

        func TestIntegrationSuite(t *testing.T) {
            suite.Run(t, new(IntegrationSuite))
        }

        // 多层继承示例
        type ServiceBaseSuite struct {
            suite.Suite
            timeout int
        }

        func (s *ServiceBaseSuite) SetupSuite() {
            s.timeout = 30
        }

        type HTTPServiceSuite struct {
            ServiceBaseSuite
            client *HTTPClient
        }

        type HTTPClient struct{}

        func (s *HTTPServiceSuite) SetupTest() {
            s.client = &HTTPClient{}
        }

        func (s *HTTPServiceSuite) TestHTTPClient() {
            s.NotNil(s.client)
            s.Equal(30, s.timeout)
        }

        func TestHTTPServiceSuite(t *testing.T) {
            suite.Run(t, new(HTTPServiceSuite))
        }
        ---

03.大型项目组织
    a.包级组织
        将测试Suite按Go包组织,每个包的测试Suite放在对应的_test包中。保持测试代码与生产代码的结构对应。使用internal/testing包存放共享的测试辅助代码、Mock、夹具工厂等。
    b.分层测试策略
        区分单元测试Suite、集成测试Suite、端到端测试Suite。使用build tags区分不同类型的测试。单元测试快速且隔离,集成测试验证组件交互,E2E测试验证完整流程。分层使测试金字塔清晰。
    c.代码示例
        ---
        // 大型项目组织示例
        package largeproject

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

        // 单元测试Suite
        type UnitTestSuite struct {
            suite.Suite
        }

        func (s *UnitTestSuite) TestBusinessLogic() {
            // 纯业务逻辑测试,无外部依赖
            result := calculateDiscount(100, 0.1)
            s.Equal(90.0, result)
        }

        func calculateDiscount(price, rate float64) float64 {
            return price * (1 - rate)
        }

        func TestUnitTestSuite(t *testing.T) {
            suite.Run(t, new(UnitTestSuite))
        }

        // 集成测试Suite
        // +build integration
        type IntegrationTestSuite struct {
            suite.Suite
            dbConn *DBConnection
        }

        func (s *IntegrationTestSuite) SetupSuite() {
            // 连接真实数据库
            s.dbConn = &DBConnection{}
        }

        func (s *IntegrationTestSuite) TestDatabaseIntegration() {
            // 测试与数据库的交互
            s.NotNil(s.dbConn)
        }

        func TestIntegrationTestSuite(t *testing.T) {
            // 使用: go test -tags=integration
            suite.Run(t, new(IntegrationTestSuite))
        }

        // E2E测试Suite
        // +build e2e
        type E2ETestSuite struct {
            suite.Suite
            server *TestServer
        }

        type TestServer struct{}

        func (s *E2ETestSuite) SetupSuite() {
            // 启动完整的测试环境
            s.server = &TestServer{}
        }

        func (s *E2ETestSuite) TestCompleteWorkflow() {
            // 测试端到端流程
            s.NotNil(s.server)
        }

        func TestE2ETestSuite(t *testing.T) {
            // 使用: go test -tags=e2e
            suite.Run(t, new(E2ETestSuite))
        }

        // 共享测试辅助
        type TestHelper struct{}

        func NewTestHelper() *TestHelper {
            return &TestHelper{}
        }

        // 多Suite协同
        type FeatureTestSuite struct {
            suite.Suite
            helper *TestHelper
        }

        func (s *FeatureTestSuite) SetupSuite() {
            s.helper = NewTestHelper()
        }

        func (s *FeatureTestSuite) TestFeatureA() {
            s.NotNil(s.helper)
        }

        func (s *FeatureTestSuite) TestFeatureB() {
            s.NotNil(s.helper)
        }

        func TestFeatureTestSuite(t *testing.T) {
            suite.Run(t, new(FeatureTestSuite))
        }
        ---

04.Suite组织最佳实践
    a.保持Suite专注
        每个Suite应该有明确的测试范围,不要让Suite变得过大。大Suite难以维护和理解。当Suite超过15-20个测试方法时,考虑拆分。根据功能、场景或测试类型拆分Suite。
    b.平衡粒度
        Suite不宜过细也不宜过粗。过细导致大量小Suite,增加管理成本。过粗导致Suite臃肿,难以定位问题。找到适合项目的平衡点。通常一个文件包含1-3个相关Suite是合理的。
    c.代码示例
        ---
        // Suite组织最佳实践
        package bestpractice

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

        // ✅ 正确:专注的Suite
        type UserAuthenticationSuite struct {
            suite.Suite
            authService *AuthService
        }

        type AuthService struct{}

        func (s *AuthService) Login(username, password string) bool {
            return username != "" && password != ""
        }

        func (s *AuthService) Logout(sessionID string) bool {
            return true
        }

        func (s *UserAuthenticationSuite) SetupTest() {
            s.authService = &AuthService{}
        }

        func (s *UserAuthenticationSuite) TestSuccessfulLogin() {
            result := s.authService.Login("user", "pass")
            s.True(result)
        }

        func (s *UserAuthenticationSuite) TestFailedLogin() {
            result := s.authService.Login("", "")
            s.False(result)
        }

        func (s *UserAuthenticationSuite) TestLogout() {
            result := s.authService.Logout("session123")
            s.True(result)
        }

        func TestUserAuthenticationSuite(t *testing.T) {
            suite.Run(t, new(UserAuthenticationSuite))
        }

        // ✅ 正确:相关功能的独立Suite
        type UserProfileSuite struct {
            suite.Suite
            profileService *ProfileService
        }

        type ProfileService struct{}

        func (s *ProfileService) GetProfile(userID int) *Profile {
            return &Profile{UserID: userID}
        }

        type Profile struct {
            UserID int
        }

        func (s *UserProfileSuite) SetupTest() {
            s.profileService = &ProfileService{}
        }

        func (s *UserProfileSuite) TestGetProfile() {
            profile := s.profileService.GetProfile(1)
            s.NotNil(profile)
        }

        func TestUserProfileSuite(t *testing.T) {
            suite.Run(t, new(UserProfileSuite))
        }

        // ❌ 错误:过大的Suite(反面示例)
        type EverythingSuite struct {
            suite.Suite
            userService    *UserService
            orderService   *OrderService
            paymentGateway *PaymentGateway
            emailService   *EmailService
            authService    *AuthService
            // ... 太多依赖
        }

        type EmailService struct{}

        // 太多不相关的测试方法
        func (s *EverythingSuite) TestUserCreation() {}
        func (s *EverythingSuite) TestUserDeletion() {}
        func (s *EverythingSuite) TestOrderProcessing() {}
        func (s *EverythingSuite) TestPaymentProcessing() {}
        func (s *EverythingSuite) TestEmailSending() {}
        func (s *EverythingSuite) TestAuthentication() {}
        // ... 20+ more methods

        // ✅ 正确:清晰的文件组织
        // user_auth_test.go -> UserAuthenticationSuite
        // user_profile_test.go -> UserProfileSuite
        // order_processing_test.go -> OrderProcessingSuite
        // payment_test.go -> PaymentProcessingSuite

        // ✅ 正确:使用子测试组织复杂场景
        type ValidationSuite struct {
            suite.Suite
        }

        func (s *ValidationSuite) TestEmailValidation() {
            s.Run("valid emails", func() {
                // 多个有效邮箱测试
            })

            s.Run("invalid emails", func() {
                // 多个无效邮箱测试
            })
        }

        func (s *ValidationSuite) TestPasswordValidation() {
            s.Run("strong passwords", func() {
                // 强密码测试
            })

            s.Run("weak passwords", func() {
                // 弱密码测试
            })
        }

        func TestValidationSuite(t *testing.T) {
            suite.Run(t, new(ValidationSuite))
        }
        ---

6 高级特性

6.1 HTTP测试工具

01.http包简介
    a.testify/http功能
        testify的http包提供了HTTP测试工具,包括TestResponseRecorder用于记录HTTP响应。可以轻松测试HTTP handler而无需启动真实服务器。支持验证响应状态码、响应头、响应体等。这使HTTP层测试变得简单高效。
    b.与httptest的关系
        testify的http包是对标准库httptest的补充和增强。提供了更友好的断言API。可以与httptest.ResponseRecorder配合使用。通常两者结合使用能覆盖大部分HTTP测试需求。
    c.代码示例
        ---
        // HTTP测试工具基础
        package httptest

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

        // 简单的HTTP handler
        func HelloHandler(w http.ResponseWriter, r *http.Request) {
            w.WriteHeader(http.StatusOK)
            w.Write([]byte("Hello, World!"))
        }

        func TestHelloHandler(t *testing.T) {
            req := httptest.NewRequest("GET", "/hello", nil)
            rec := httptest.NewRecorder()

            HelloHandler(rec, req)

            assert.Equal(t, http.StatusOK, rec.Code)
            assert.Equal(t, "Hello, World!", rec.Body.String())
        }

        // JSON响应handler
        func JSONHandler(w http.ResponseWriter, r *http.Request) {
            w.Header().Set("Content-Type", "application/json")
            w.WriteHeader(http.StatusOK)
            w.Write([]byte(`{"message":"success"}`))
        }

        func TestJSONHandler(t *testing.T) {
            req := httptest.NewRequest("GET", "/api/data", nil)
            rec := httptest.NewRecorder()

            JSONHandler(rec, req)

            assert.Equal(t, http.StatusOK, rec.Code)
            assert.Equal(t, "application/json", rec.Header().Get("Content-Type"))
            assert.Contains(t, rec.Body.String(), "success")
        }

        // 带参数的handler
        func EchoHandler(w http.ResponseWriter, r *http.Request) {
            name := r.URL.Query().Get("name")
            if name == "" {
                w.WriteHeader(http.StatusBadRequest)
                w.Write([]byte("name parameter required"))
                return
            }
            w.WriteHeader(http.StatusOK)
            w.Write([]byte("Hello, " + name))
        }

        func TestEchoHandler(t *testing.T) {
            t.Run("with name", func(t *testing.T) {
                req := httptest.NewRequest("GET", "/echo?name=Alice", nil)
                rec := httptest.NewRecorder()

                EchoHandler(rec, req)

                assert.Equal(t, http.StatusOK, rec.Code)
                assert.Equal(t, "Hello, Alice", rec.Body.String())
            })

            t.Run("without name", func(t *testing.T) {
                req := httptest.NewRequest("GET", "/echo", nil)
                rec := httptest.NewRecorder()

                EchoHandler(rec, req)

                assert.Equal(t, http.StatusBadRequest, rec.Code)
                assert.Contains(t, rec.Body.String(), "required")
            })
        }

        // POST请求测试
        func CreateHandler(w http.ResponseWriter, r *http.Request) {
            if r.Method != http.MethodPost {
                w.WriteHeader(http.StatusMethodNotAllowed)
                return
            }

            w.WriteHeader(http.StatusCreated)
            w.Write([]byte("created"))
        }

        func TestCreateHandler(t *testing.T) {
            req := httptest.NewRequest("POST", "/create", nil)
            rec := httptest.NewRecorder()

            CreateHandler(rec, req)

            assert.Equal(t, http.StatusCreated, rec.Code)
            assert.Equal(t, "created", rec.Body.String())
        }
        ---

02.测试HTTP客户端
    a.Mock HTTP服务器
        使用httptest.NewServer创建测试HTTP服务器。服务器在随机端口监听,返回预定义的响应。测试完成后记得关闭服务器。这种方式可以测试HTTP客户端代码而不依赖真实服务。
    b.请求验证
        在测试服务器的handler中可以验证请求的method、headers、body等。记录收到的请求,在测试中断言。这确保HTTP客户端发送了正确的请求。
    c.代码示例
        ---
        // 测试HTTP客户端
        package httpclient

        import (
            "io"
            "net/http"
            "net/http/httptest"
            "testing"
            "github.com/stretchr/testify/assert"
            "github.com/stretchr/testify/require"
        )

        // HTTP客户端
        type APIClient struct {
            baseURL string
            client  *http.Client
        }

        func NewAPIClient(baseURL string) *APIClient {
            return &APIClient{
                baseURL: baseURL,
                client:  &http.Client{},
            }
        }

        func (c *APIClient) GetUser(id string) (string, error) {
            resp, err := c.client.Get(c.baseURL + "/users/" + id)
            if err != nil {
                return "", err
            }
            defer resp.Body.Close()

            body, err := io.ReadAll(resp.Body)
            if err != nil {
                return "", err
            }

            return string(body), nil
        }

        // 测试HTTP客户端
        func TestAPIClient_GetUser(t *testing.T) {
            // 创建测试服务器
            server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                assert.Equal(t, "/users/123", r.URL.Path)
                assert.Equal(t, "GET", r.Method)

                w.WriteHeader(http.StatusOK)
                w.Write([]byte(`{"id":"123","name":"Alice"}`))
            }))
            defer server.Close()

            // 创建客户端,指向测试服务器
            client := NewAPIClient(server.URL)

            // 执行请求
            result, err := client.GetUser("123")

            require.NoError(t, err)
            assert.Contains(t, result, "Alice")
        }

        // 测试错误场景
        func TestAPIClient_ServerError(t *testing.T) {
            server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                w.WriteHeader(http.StatusInternalServerError)
                w.Write([]byte("server error"))
            }))
            defer server.Close()

            client := NewAPIClient(server.URL)
            result, err := client.GetUser("999")

            require.NoError(t, err)
            assert.Contains(t, result, "error")
        }

        // 测试超时
        func TestAPIClient_Timeout(t *testing.T) {
            server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                // 模拟慢速响应
                // time.Sleep(2 * time.Second)
                w.WriteHeader(http.StatusOK)
            }))
            defer server.Close()

            client := NewAPIClient(server.URL)
            _, err := client.GetUser("1")

            // 这个示例不会超时,实际测试需要设置超时
            assert.NoError(t, err)
        }
        ---

6.2 表格驱动测试

01.表格驱动模式
    a.基本概念
        表格驱动测试使用数据结构数组定义测试用例,每个用例包含输入和期望输出。遍历数组,对每个用例执行相同的测试逻辑。这种模式减少重复代码,使测试用例清晰可见,易于添加新用例。
    b.优势
        测试逻辑和测试数据分离,提高可维护性。添加新测试用例只需添加数据,无需重复代码。测试用例一目了然,便于review和理解。适合测试相同逻辑的多种输入组合。
    c.代码示例
        ---
        // 表格驱动测试基础
        package tabledriven

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

        // 被测函数
        func Add(a, b int) int {
            return a + b
        }

        func TestAdd(t *testing.T) {
            tests := []struct {
                name     string
                a, b     int
                expected int
            }{
                {"positive numbers", 2, 3, 5},
                {"negative numbers", -2, -3, -5},
                {"mixed sign", 10, -5, 5},
                {"zero", 0, 0, 0},
                {"with zero", 5, 0, 5},
            }

            for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
                    result := Add(tt.a, tt.b)
                    assert.Equal(t, tt.expected, result)
                })
            }
        }

        // 复杂测试用例
        func ValidateEmail(email string) bool {
            return len(email) > 3 && email[0] != '@'
        }

        func TestValidateEmail(t *testing.T) {
            tests := []struct {
                name     string
                email    string
                expected bool
            }{
                {"valid email", "[email protected]", true},
                {"valid with subdomain", "[email protected]", true},
                {"invalid - no @", "userexample.com", true},
                {"invalid - starts with @", "@example.com", false},
                {"invalid - empty", "", false},
                {"invalid - too short", "a@b", false},
            }

            for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
                    result := ValidateEmail(tt.email)
                    assert.Equal(t, tt.expected, result,
                        "ValidateEmail(%q) should be %v", tt.email, tt.expected)
                })
            }
        }

        // 带错误的测试用例
        func Divide(a, b int) (int, error) {
            if b == 0 {
                return 0, assert.AnError
            }
            return a / b, nil
        }

        func TestDivide(t *testing.T) {
            tests := []struct {
                name        string
                a, b        int
                expectedVal int
                expectError bool
            }{
                {"normal division", 10, 2, 5, false},
                {"division by zero", 10, 0, 0, true},
                {"negative result", -10, 2, -5, false},
            }

            for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
                    result, err := Divide(tt.a, tt.b)

                    if tt.expectError {
                        assert.Error(t, err)
                    } else {
                        assert.NoError(t, err)
                        assert.Equal(t, tt.expectedVal, result)
                    }
                })
            }
        }
        ---

02.与Suite结合
    a.Suite中的表格驱动
        在testify Suite中使用表格驱动测试结合了两者的优势。Suite提供setup/teardown和共享状态,表格驱动提供清晰的测试用例组织。在Suite的测试方法中定义测试表格并遍历。
    b.共享夹具访问
        表格驱动测试中的子测试可以访问Suite的字段和方法。可以在setup中准备数据,在表格用例中使用。这避免了在每个用例中重复初始化。
    c.代码示例
        ---
        // Suite与表格驱动结合
        package suitetable

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

        type CalculatorSuite struct {
            suite.Suite
            calculator *Calculator
        }

        type Calculator struct{}

        func (c *Calculator) Add(a, b int) int      { return a + b }
        func (c *Calculator) Subtract(a, b int) int { return a - b }
        func (c *Calculator) Multiply(a, b int) int { return a * b }

        func (s *CalculatorSuite) SetupTest() {
            s.calculator = &Calculator{}
        }

        func (s *CalculatorSuite) TestAddition() {
            tests := []struct {
                name     string
                a, b     int
                expected int
            }{
                {"2+3", 2, 3, 5},
                {"0+0", 0, 0, 0},
                {"-1+1", -1, 1, 0},
            }

            for _, tt := range tests {
                s.Run(tt.name, func() {
                    result := s.calculator.Add(tt.a, tt.b)
                    s.Equal(tt.expected, result)
                })
            }
        }

        func (s *CalculatorSuite) TestSubtraction() {
            tests := []struct {
                name     string
                a, b     int
                expected int
            }{
                {"5-3", 5, 3, 2},
                {"0-0", 0, 0, 0},
                {"1-2", 1, 2, -1},
            }

            for _, tt := range tests {
                s.Run(tt.name, func() {
                    result := s.calculator.Subtract(tt.a, tt.b)
                    s.Equal(tt.expected, result)
                })
            }
        }

        func TestCalculatorSuite(t *testing.T) {
            suite.Run(t, new(CalculatorSuite))
        }

        // 复杂示例:使用Suite夹具
        type UserServiceSuite struct {
            suite.Suite
            service   *UserService
            testUsers []*User
        }

        type UserService struct {
            users map[int]*User
        }

        func (s *UserService) GetUser(id int) *User {
            return s.users[id]
        }

        type User struct {
            ID   int
            Name string
            Age  int
        }

        func (s *UserServiceSuite) SetupTest() {
            s.service = &UserService{
                users: make(map[int]*User),
            }

            s.testUsers = []*User{
                {ID: 1, Name: "Alice", Age: 25},
                {ID: 2, Name: "Bob", Age: 30},
                {ID: 3, Name: "Charlie", Age: 35},
            }

            for _, user := range s.testUsers {
                s.service.users[user.ID] = user
            }
        }

        func (s *UserServiceSuite) TestGetUser() {
            tests := []struct {
                name     string
                userID   int
                expected *User
            }{
                {"get Alice", 1, s.testUsers[0]},
                {"get Bob", 2, s.testUsers[1]},
                {"get Charlie", 3, s.testUsers[2]},
            }

            for _, tt := range tests {
                s.Run(tt.name, func() {
                    user := s.service.GetUser(tt.userID)
                    s.Equal(tt.expected, user)
                })
            }
        }

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

6.3 测试覆盖率

01.覆盖率统计
    a.go test -cover
        使用go test -cover运行测试并显示覆盖率百分比。覆盖率表示被测试执行到的代码行数占总代码行数的比例。高覆盖率不等于高质量,但低覆盖率通常意味着测试不充分。覆盖率是测试完整性的一个重要指标。
    b.覆盖率报告
        使用-coverprofile生成覆盖率文件,用go tool cover -html查看HTML报告。报告以颜色标示哪些代码被覆盖(绿色)、哪些未覆盖(红色)。这帮助识别测试盲点,指导补充测试用例。
    c.代码示例
        ---
        // 覆盖率测试示例
        package coverage

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

        // 被测函数
        func ProcessData(input string) string {
            if input == "" {
                return "empty"
            }

            if len(input) < 3 {
                return "short"
            }

            if input == "special" {
                return "special case"
            }

            return "normal: " + input
        }

        // 测试覆盖所有分支
        func TestProcessData_FullCoverage(t *testing.T) {
            tests := []struct {
                input    string
                expected string
            }{
                {"", "empty"},
                {"ab", "short"},
                {"special", "special case"},
                {"normal input", "normal: normal input"},
            }

            for _, tt := range tests {
                t.Run(tt.input, func(t *testing.T) {
                    result := ProcessData(tt.input)
                    assert.Equal(t, tt.expected, result)
                })
            }
        }

        // 运行命令:
        // go test -cover
        // go test -coverprofile=coverage.out
        // go tool cover -html=coverage.out
        ---

02.提高覆盖率策略
    a.识别未覆盖代码
        查看覆盖率报告,找出未被测试的代码路径。关注错误处理分支、边界条件、异常场景。优先为核心业务逻辑和复杂逻辑增加测试。不要为了覆盖率而测试,要关注测试的价值。
    b.边界值测试
        测试函数在边界条件下的行为如空输入、最大值、最小值等。边界往往是bug高发区。表格驱动测试很适合覆盖多种边界情况。确保所有if、switch分支都被至少测试一次。
    c.代码示例
        ---
        // 提高覆盖率示例
        package improvecoverage

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

        func ValidateAge(age int) error {
            if age < 0 {
                return errors.New("age cannot be negative")
            }
            if age > 150 {
                return errors.New("age too large")
            }
            if age < 18 {
                return errors.New("must be adult")
            }
            return nil
        }

        // 完整覆盖所有分支
        func TestValidateAge(t *testing.T) {
            tests := []struct {
                name        string
                age         int
                expectError bool
                errorMsg    string
            }{
                {"negative age", -1, true, "negative"},
                {"too old", 200, true, "too large"},
                {"minor", 10, true, "adult"},
                {"adult", 25, false, ""},
                {"boundary min", 18, false, ""},
                {"boundary max", 150, false, ""},
            }

            for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
                    err := ValidateAge(tt.age)

                    if tt.expectError {
                        assert.Error(t, err)
                        if tt.errorMsg != "" {
                            assert.Contains(t, err.Error(), tt.errorMsg)
                        }
                    } else {
                        assert.NoError(t, err)
                    }
                })
            }
        }
        ---

6.4 基准测试集成

01.基准测试基础
    a.Benchmark函数
        Go的基准测试函数以Benchmark开头,接收*testing.B参数。在循环中执行被测代码b.N次,Go自动调整N直到结果稳定。使用go test -bench运行基准测试。基准测试衡量代码性能,识别瓶颈。
    b.与testify结合
        testify的断言可以在基准测试的setup阶段使用,但不要在b.N循环内使用断言,会影响性能测量。可以在基准测试后验证结果正确性。基准测试关注性能,单元测试关注正确性,两者互补。
    c.代码示例
        ---
        // 基准测试示例
        package bench

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

        func Fibonacci(n int) int {
            if n <= 1 {
                return n
            }
            return Fibonacci(n-1) + Fibonacci(n-2)
        }

        func BenchmarkFibonacci(b *testing.B) {
            for i := 0; i < b.N; i++ {
                Fibonacci(20)
            }
        }

        // 不同输入的基准测试
        func BenchmarkFibonacci10(b *testing.B) {
            for i := 0; i < b.N; i++ {
                Fibonacci(10)
            }
        }

        func BenchmarkFibonacci20(b *testing.B) {
            for i := 0; i < b.N; i++ {
                Fibonacci(20)
            }
        }

        // 使用testify验证结果
        func TestFibonacciCorrectness(t *testing.T) {
            assert.Equal(t, 0, Fibonacci(0))
            assert.Equal(t, 1, Fibonacci(1))
            assert.Equal(t, 55, Fibonacci(10))
        }

        // 运行:go test -bench=. -benchmem
        ---

6.5 并发安全测试

01.Data Race检测
    a.go test -race
        使用-race标志运行测试,启用Go的data race detector。检测并发访问共享变量时的竞态条件。发现race时会输出详细报告包括冲突的goroutine和代码位置。所有并发代码都应该用-race测试。
    b.常见竞态场景
        多个goroutine同时读写map。多个goroutine修改同一变量。闭包捕获循环变量。使用mutex、channel或atomic操作保护共享状态。testify的断言本身不是并发安全的。
    c.代码示例
        ---
        // 并发安全测试
        package racefree

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

        // ❌ 不安全的计数器
        type UnsafeCounter struct {
            count int
        }

        func (c *UnsafeCounter) Inc() {
            c.count++
        }

        // ✅ 安全的计数器
        type SafeCounter struct {
            mu    sync.Mutex
            count int
        }

        func (c *SafeCounter) Inc() {
            c.mu.Lock()
            c.count++
            c.mu.Unlock()
        }

        func (c *SafeCounter) Value() int {
            c.mu.Lock()
            defer c.mu.Unlock()
            return c.count
        }

        func TestSafeCounter(t *testing.T) {
            counter := &SafeCounter{}
            var wg sync.WaitGroup

            for i := 0; i < 1000; i++ {
                wg.Add(1)
                go func() {
                    defer wg.Done()
                    counter.Inc()
                }()
            }

            wg.Wait()
            assert.Equal(t, 1000, counter.Value())
        }

        // 使用atomic的计数器
        type AtomicCounter struct {
            count int64
        }

        func (c *AtomicCounter) Inc() {
            atomic.AddInt64(&c.count, 1)
        }

        func (c *AtomicCounter) Value() int64 {
            return atomic.LoadInt64(&c.count)
        }

        func TestAtomicCounter(t *testing.T) {
            counter := &AtomicCounter{}
            var wg sync.WaitGroup

            for i := 0; i < 1000; i++ {
                wg.Add(1)
                go func() {
                    defer wg.Done()
                    counter.Inc()
                }()
            }

            wg.Wait()
            assert.Equal(t, int64(1000), counter.Value())
        }

        // 运行:go test -race
        ---

6.6 测试辅助函数

01.自定义断言辅助
    a.封装复杂断言
        将重复的复杂断言封装成辅助函数。辅助函数接收*testing.T和待验证的值。这减少重复代码,使测试更清晰。辅助函数应该调用t.Helper()标记自己为helper,错误时报告正确的行号。
    b.领域特定断言
        为项目特定的数据类型创建断言函数。例如验证自定义结构体、检查业务规则等。这使测试代码更贴近业务语言,提高可读性。辅助函数可以在多个测试间复用。
    c.代码示例
        ---
        // 测试辅助函数
        package helpers

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

        type User struct {
            ID    int
            Name  string
            Email string
            Age   int
        }

        // 自定义断言辅助函数
        func AssertValidUser(t *testing.T, user *User) {
            t.Helper()

            assert.NotNil(t, user)
            assert.Greater(t, user.ID, 0)
            assert.NotEmpty(t, user.Name)
            assert.Contains(t, user.Email, "@")
            assert.GreaterOrEqual(t, user.Age, 0)
        }

        func TestUserCreation(t *testing.T) {
            user := &User{
                ID:    1,
                Name:  "Alice",
                Email: "[email protected]",
                Age:   25,
            }

            AssertValidUser(t, user)
        }

        // 领域特定断言
        func AssertAdultUser(t *testing.T, user *User) {
            t.Helper()

            AssertValidUser(t, user)
            assert.GreaterOrEqual(t, user.Age, 18, "user must be adult")
        }

        func TestAdultUser(t *testing.T) {
            user := &User{ID: 1, Name: "Bob", Email: "[email protected]", Age: 30}
            AssertAdultUser(t, user)
        }

        // 集合验证辅助
        func AssertAllUsersValid(t *testing.T, users []*User) {
            t.Helper()

            assert.NotEmpty(t, users)
            for i, user := range users {
                assert.NotNil(t, user, "user at index %d should not be nil", i)
                AssertValidUser(t, user)
            }
        }

        func TestUserList(t *testing.T) {
            users := []*User{
                {ID: 1, Name: "Alice", Email: "[email protected]", Age: 25},
                {ID: 2, Name: "Bob", Email: "[email protected]", Age: 30},
            }

            AssertAllUsersValid(t, users)
        }
        ---

6.7 与CI/CD集成

01.持续集成配置
    a.GitHub Actions
        在.github/workflows创建workflow文件配置CI。运行go test执行测试,使用go test -race检测竞态条件。可以配置多个Go版本矩阵测试。CI确保每次提交都运行测试,及早发现问题。
    b.测试报告
        使用-json输出JSON格式的测试结果,便于CI系统解析。配置覆盖率上传到Codecov或Coveralls。设置测试失败时阻止合并。CI提供可视化的测试结果和趋势。
    c.代码示例
        ---
        # GitHub Actions配置示例
        # .github/workflows/test.yml
        name: Tests

        on: [push, pull_request]

        jobs:
          test:
            runs-on: ubuntu-latest
            strategy:
              matrix:
                go-version: [1.20, 1.21, 1.22]

            steps:
            - uses: actions/checkout@v3

            - name: Set up Go
              uses: actions/setup-go@v4
              with:
                go-version: ${{ matrix.go-version }}

            - name: Install dependencies
              run: go mod download

            - name: Run tests
              run: go test -v -race -coverprofile=coverage.out ./...

            - name: Upload coverage
              uses: codecov/codecov-action@v3
              with:
                file: ./coverage.out
        ---

02.测试优化
    a.并行测试
        使用t.Parallel()标记可并行的测试。CI环境通常有多核CPU,并行测试大幅减少总时间。确保并行测试间完全独立。可以配置GOMAXPROCS控制并行度。
    b.测试缓存
        Go自动缓存测试结果,未改变的测试不重复运行。使用go clean -testcache清除缓存。CI环境可以缓存go module和build cache加速。
    c.代码示例
        ---
        // CI优化示例
        package cioptimize

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

        // 快速单元测试 - 总是并行
        func TestFastUnit(t *testing.T) {
            t.Parallel()
            assert.Equal(t, 4, 2+2)
        }

        // 慢速集成测试 - 使用build tag隔离
        // +build integration
        func TestSlowIntegration(t *testing.T) {
            // 不并行,需要独占资源
            assert.True(t, true)
        }

        // CI命令示例:
        // 快速反馈:go test -short ./...
        // 完整测试:go test -v ./...
        // 集成测试:go test -tags=integration ./...
        ---

6.8 测试数据生成

01.测试数据生成策略
    a.手动构造
        对于简单场景,手动创建测试数据最直接。使用字面量或简单的构造函数。适合数据结构简单、用例少的情况。手动数据易于理解和维护。
    b.工厂函数
        创建工厂函数生成常用的测试数据。工厂可以接收参数定制数据。复用工厂函数减少重复代码。可以为不同场景创建多个工厂变体。
    c.代码示例
        ---
        // 测试数据生成
        package testdata

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

        type Product struct {
            ID    int
            Name  string
            Price float64
            Stock int
        }

        // 简单工厂
        func NewTestProduct(id int) *Product {
            return &Product{
                ID:    id,
                Name:  fmt.Sprintf("Product%d", id),
                Price: 99.99,
                Stock: 100,
            }
        }

        // 可定制工厂
        func NewTestProductWithPrice(id int, price float64) *Product {
            p := NewTestProduct(id)
            p.Price = price
            return p
        }

        // Builder模式生成
        type ProductBuilder struct {
            product *Product
        }

        func NewProductBuilder() *ProductBuilder {
            return &ProductBuilder{
                product: &Product{
                    Price: 0,
                    Stock: 0,
                },
            }
        }

        func (b *ProductBuilder) WithID(id int) *ProductBuilder {
            b.product.ID = id
            return b
        }

        func (b *ProductBuilder) WithName(name string) *ProductBuilder {
            b.product.Name = name
            return b
        }

        func (b *ProductBuilder) WithPrice(price float64) *ProductBuilder {
            b.product.Price = price
            return b
        }

        func (b *ProductBuilder) Build() *Product {
            return b.product
        }

        func TestProductCreation(t *testing.T) {
            // 使用工厂
            p1 := NewTestProduct(1)
            assert.Equal(t, 1, p1.ID)

            // 使用Builder
            p2 := NewProductBuilder().
                WithID(2).
                WithName("Custom").
                WithPrice(49.99).
                Build()

            assert.Equal(t, "Custom", p2.Name)
            assert.Equal(t, 49.99, p2.Price)
        }

        // 批量生成
        func GenerateTestProducts(count int) []*Product {
            products := make([]*Product, count)
            for i := 0; i < count; i++ {
                products[i] = NewTestProduct(i + 1)
            }
            return products
        }

        func TestBulkProducts(t *testing.T) {
            products := GenerateTestProducts(10)
            assert.Len(t, products, 10)
        }
        ---

02.随机数据与属性测试
    a.随机测试数据
        使用随机数生成测试数据可以发现边界情况。设置固定seed确保测试可重复。随机测试不应替代精心设计的测试用例,而是补充。记录seed值以便复现失败。
    b.属性测试思想
        属性测试验证代码的通用属性而非具体输入输出。例如测试排序函数的结果总是有序的。Go生态有gopter等属性测试库。testify主要用于传统基于示例的测试。
    c.代码示例
        ---
        // 随机测试数据
        package random

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

        func init() {
            // 设置固定seed使测试可重复
            rand.Seed(42)
        }

        func GenerateRandomUser() *User {
            return &User{
                ID:   rand.Intn(10000),
                Name: fmt.Sprintf("User%d", rand.Intn(1000)),
                Age:  rand.Intn(80) + 18,
            }
        }

        func TestRandomUsers(t *testing.T) {
            for i := 0; i < 100; i++ {
                user := GenerateRandomUser()

                // 验证不变属性
                assert.Greater(t, user.ID, 0)
                assert.NotEmpty(t, user.Name)
                assert.GreaterOrEqual(t, user.Age, 18)
            }
        }

        // 属性测试示例
        func Reverse(s string) string {
            runes := []rune(s)
            for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
                runes[i], runes[j] = runes[j], runes[i]
            }
            return string(runes)
        }

        func TestReverseProperty(t *testing.T) {
            // 属性:反转两次得到原字符串
            inputs := []string{"hello", "world", "test", "12345", ""}

            for _, input := range inputs {
                result := Reverse(Reverse(input))
                assert.Equal(t, input, result,
                    "Reverse(Reverse(x)) should equal x")
            }
        }
        ---

7 实战应用

7.1 安装与配置

01.安装testify
    a.使用go get
        执行go get github.com/stretchr/testify安装testify。这会下载testify及其依赖到项目的go.mod文件。testify是纯Go实现,无需额外依赖。安装后即可在测试文件中import使用。
    b.版本管理
        使用go.mod管理testify版本。执行go get github.com/stretchr/testify@latest获取最新版本。可以指定特定版本如@v1.8.4。使用go mod tidy清理未使用的依赖。定期更新获取bug修复和新特性。
    c.代码示例
        ---
        // 安装命令
        // $ go get github.com/stretchr/testify

        // go.mod示例
        module myproject

        go 1.21

        require (
            github.com/stretchr/testify v1.8.4
        )

        // 基本使用示例
        package main

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

        func TestBasicSetup(t *testing.T) {
            // 使用assert
            assert.Equal(t, 2, 1+1)

            // 使用require
            require.NotNil(t, &struct{}{})
        }

        // 版本更新命令
        // $ go get -u github.com/stretchr/testify
        // $ go get github.com/stretchr/[email protected]
        // $ go mod tidy
        ---

02.项目结构
    a.测试文件组织
        测试文件与源文件放在同一目录,命名为xxx_test.go。使用package xxx_test创建黑盒测试,或package xxx创建白盒测试。黑盒测试只能访问导出的API,更接近用户视角。白盒测试可以访问内部实现,便于测试内部逻辑。
    b.测试辅助代码
        创建testing包或testutil目录存放共享的测试辅助代码。包括Mock定义、测试数据工厂、自定义断言等。避免在生产代码中引用测试辅助代码。使用internal/testing限制测试辅助代码的可见性。
    c.代码示例
        ---
        // 项目结构示例
        myproject/
        ├── go.mod
        ├── go.sum
        ├── cmd/
        │   └── app/
        │       ├── main.go
        │       └── main_test.go
        ├── internal/
        │   ├── service/
        │   │   ├── user.go
        │   │   └── user_test.go
        │   └── testing/
        │       ├── mocks.go
        │       └── factories.go
        ├── pkg/
        │   └── api/
        │       ├── handler.go
        │       └── handler_test.go
        └── test/
            ├── integration/
            │   └── api_test.go
            └── e2e/
                └── workflow_test.go

        // 黑盒测试示例 (package xxx_test)
        package service_test

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

        func TestUserService(t *testing.T) {
            svc := service.NewUserService()
            // 只能访问导出的方法
            assert.NotNil(t, svc)
        }

        // 白盒测试示例 (package xxx)
        package service

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

        func TestInternalLogic(t *testing.T) {
            // 可以访问未导出的函数和字段
            result := internalHelper()
            assert.NotEmpty(t, result)
        }

        func internalHelper() string {
            return "internal"
        }
        ---

03.IDE集成
    a.VSCode配置
        安装Go扩展获得测试支持。可以在测试函数上方点击"run test"或"debug test"。配置settings.json设置测试参数如-v、-race。使用Test Explorer查看和运行所有测试。
    b.GoLand配置
        GoLand内置testify支持,自动识别测试。右键测试函数选择Run或Debug。可以配置Run Configuration添加测试参数。查看测试覆盖率高亮显示覆盖的代码。
    c.代码示例
        ---
        // VSCode settings.json配置
        {
            "go.testFlags": ["-v", "-race"],
            "go.coverOnSave": true,
            "go.coverageDecorator": {
                "type": "gutter"
            }
        }

        // GoLand Run Configuration
        // Run > Edit Configurations > Go Test
        // Program arguments: -v -race -cover

        // 命令行运行
        // $ go test -v ./...
        // $ go test -v -race -cover ./...
        // $ go test -v -run TestSpecific
        ---

7.2 单元测试实战

01.业务逻辑测试
    a.测试纯函数
        纯函数没有副作用,输入确定则输出确定。是最容易测试的代码。使用表格驱动测试覆盖多种输入。纯函数测试快速且可靠,应该占单元测试的大部分。
    b.测试有状态对象
        对于有内部状态的对象,在SetupTest中初始化干净状态。测试状态转换逻辑,验证操作后状态正确。使用Suite组织相关的状态测试。每个测试应该独立,不依赖其他测试的状态。
    c.代码示例
        ---
        // 单元测试实战
        package unittest

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

        // 纯函数测试
        func CalculateDiscount(price float64, percent float64) float64 {
            if percent < 0 || percent > 100 {
                return price
            }
            return price * (1 - percent/100)
        }

        func TestCalculateDiscount(t *testing.T) {
            tests := []struct {
                name     string
                price    float64
                percent  float64
                expected float64
            }{
                {"10% off", 100.0, 10, 90.0},
                {"50% off", 100.0, 50, 50.0},
                {"no discount", 100.0, 0, 100.0},
                {"invalid negative", 100.0, -10, 100.0},
                {"invalid over 100", 100.0, 150, 100.0},
            }

            for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
                    result := CalculateDiscount(tt.price, tt.percent)
                    assert.InDelta(t, tt.expected, result, 0.01)
                })
            }
        }

        // 有状态对象测试
        type ShoppingCart struct {
            items []Item
            total float64
        }

        type Item struct {
            Name  string
            Price float64
            Qty   int
        }

        func NewShoppingCart() *ShoppingCart {
            return &ShoppingCart{
                items: make([]Item, 0),
                total: 0,
            }
        }

        func (c *ShoppingCart) AddItem(item Item) {
            c.items = append(c.items, item)
            c.total += item.Price * float64(item.Qty)
        }

        func (c *ShoppingCart) RemoveItem(name string) error {
            for i, item := range c.items {
                if item.Name == name {
                    c.total -= item.Price * float64(item.Qty)
                    c.items = append(c.items[:i], c.items[i+1:]...)
                    return nil
                }
            }
            return errors.New("item not found")
        }

        func (c *ShoppingCart) GetTotal() float64 {
            return c.total
        }

        type ShoppingCartSuite struct {
            suite.Suite
            cart *ShoppingCart
        }

        func (s *ShoppingCartSuite) SetupTest() {
            s.cart = NewShoppingCart()
        }

        func (s *ShoppingCartSuite) TestAddItem() {
            item := Item{Name: "Product1", Price: 10.0, Qty: 2}
            s.cart.AddItem(item)

            s.Len(s.cart.items, 1)
            s.Equal(20.0, s.cart.GetTotal())
        }

        func (s *ShoppingCartSuite) TestAddMultipleItems() {
            s.cart.AddItem(Item{Name: "P1", Price: 10.0, Qty: 1})
            s.cart.AddItem(Item{Name: "P2", Price: 20.0, Qty: 2})

            s.Len(s.cart.items, 2)
            s.Equal(50.0, s.cart.GetTotal())
        }

        func (s *ShoppingCartSuite) TestRemoveItem() {
            s.cart.AddItem(Item{Name: "P1", Price: 10.0, Qty: 1})
            s.cart.AddItem(Item{Name: "P2", Price: 20.0, Qty: 1})

            err := s.cart.RemoveItem("P1")

            s.NoError(err)
            s.Len(s.cart.items, 1)
            s.Equal(20.0, s.cart.GetTotal())
        }

        func (s *ShoppingCartSuite) TestRemoveNonexistentItem() {
            err := s.cart.RemoveItem("nonexistent")
            s.Error(err)
        }

        func TestShoppingCartSuite(t *testing.T) {
            suite.Run(t, new(ShoppingCartSuite))
        }
        ---

02.错误处理测试
    a.预期错误测试
        使用assert.Error验证函数返回错误。使用assert.ErrorIs或assert.ErrorAs验证特定错误类型。测试错误消息是否包含必要信息。错误处理是代码健壮性的关键,必须充分测试。
    b.错误场景覆盖
        测试各种可能导致错误的输入。包括nil参数、无效参数、资源不可用等。确保错误路径也被测试覆盖。错误测试帮助发现异常场景下的问题。
    c.代码示例
        ---
        // 错误处理测试
        package errorhandling

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

        var (
            ErrInvalidInput = errors.New("invalid input")
            ErrNotFound     = errors.New("not found")
        )

        func ProcessUser(id int) (*User, error) {
            if id <= 0 {
                return nil, ErrInvalidInput
            }
            if id == 999 {
                return nil, ErrNotFound
            }
            return &User{ID: id, Name: "User"}, nil
        }

        func TestProcessUser_Success(t *testing.T) {
            user, err := ProcessUser(1)

            assert.NoError(t, err)
            assert.NotNil(t, user)
            assert.Equal(t, 1, user.ID)
        }

        func TestProcessUser_InvalidInput(t *testing.T) {
            user, err := ProcessUser(-1)

            assert.Error(t, err)
            assert.Nil(t, user)
            assert.ErrorIs(t, err, ErrInvalidInput)
        }

        func TestProcessUser_NotFound(t *testing.T) {
            user, err := ProcessUser(999)

            assert.Error(t, err)
            assert.Nil(t, user)
            assert.ErrorIs(t, err, ErrNotFound)
        }

        // 表格驱动错误测试
        func TestProcessUser_AllCases(t *testing.T) {
            tests := []struct {
                name        string
                id          int
                expectError bool
                expectedErr error
            }{
                {"valid", 1, false, nil},
                {"zero id", 0, true, ErrInvalidInput},
                {"negative id", -1, true, ErrInvalidInput},
                {"not found", 999, true, ErrNotFound},
            }

            for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
                    user, err := ProcessUser(tt.id)

                    if tt.expectError {
                        assert.Error(t, err)
                        assert.Nil(t, user)
                        if tt.expectedErr != nil {
                            assert.ErrorIs(t, err, tt.expectedErr)
                        }
                    } else {
                        assert.NoError(t, err)
                        assert.NotNil(t, user)
                    }
                })
            }
        }
        ---

7.3 集成测试实战

01.数据库集成测试
    a.测试数据库设置
        使用测试数据库或容器化数据库如docker的MySQL/PostgreSQL。在SetupSuite中建立数据库连接,在TearDownSuite中关闭。在SetupTest中准备测试数据,在TearDownTest中清理。确保测试数据不污染生产环境。
    b.事务回滚策略
        在测试中使用事务,测试结束后回滚保持数据库干净。每个测试在事务中执行,互不影响。这比清空表更快且更安全。适合大多数数据库集成测试场景。
    c.代码示例
        ---
        // 数据库集成测试
        package dbintegration

        import (
            "database/sql"
            "testing"
            "github.com/stretchr/testify/suite"
        )

        type DatabaseSuite struct {
            suite.Suite
            db *sql.DB
        }

        func (s *DatabaseSuite) SetupSuite() {
            // 连接测试数据库
            var err error
            s.db, err = sql.Open("mysql", "test:test@tcp(localhost:3306)/testdb")
            s.Require().NoError(err)

            // 创建测试表
            _, err = s.db.Exec(`
                CREATE TABLE IF NOT EXISTS users (
                    id INT PRIMARY KEY AUTO_INCREMENT,
                    name VARCHAR(100),
                    email VARCHAR(100)
                )
            `)
            s.Require().NoError(err)
        }

        func (s *DatabaseSuite) TearDownSuite() {
            // 清理并关闭连接
            s.db.Exec("DROP TABLE IF EXISTS users")
            s.db.Close()
        }

        func (s *DatabaseSuite) TearDownTest() {
            // 每个测试后清空数据
            s.db.Exec("DELETE FROM users")
        }

        func (s *DatabaseSuite) TestInsertUser() {
            result, err := s.db.Exec(
                "INSERT INTO users (name, email) VALUES (?, ?)",
                "Alice", "[email protected]",
            )

            s.NoError(err)
            id, err := result.LastInsertId()
            s.NoError(err)
            s.Greater(id, int64(0))
        }

        func (s *DatabaseSuite) TestQueryUser() {
            // 插入测试数据
            s.db.Exec("INSERT INTO users (name, email) VALUES (?, ?)",
                "Bob", "[email protected]")

            // 查询
            var name, email string
            err := s.db.QueryRow("SELECT name, email FROM users WHERE name = ?", "Bob").
                Scan(&name, &email)

            s.NoError(err)
            s.Equal("Bob", name)
            s.Equal("[email protected]", email)
        }

        func TestDatabaseSuite(t *testing.T) {
            suite.Run(t, new(DatabaseSuite))
        }
        ---

02.HTTP API集成测试
    a.测试HTTP服务
        使用httptest.NewServer启动测试服务器。发送真实的HTTP请求并验证响应。测试完整的请求-响应流程包括路由、中间件、handler。比单独测试handler更接近真实场景。
    b.端到端API测试
        测试完整的API工作流程如用户注册、登录、操作资源。使用真实的HTTP客户端或测试客户端。验证状态码、响应体、响应头。可以测试认证、授权、错误处理等。
    c.代码示例
        ---
        // HTTP API集成测试
        package apiintegration

        import (
            "encoding/json"
            "net/http"
            "net/http/httptest"
            "strings"
            "testing"
            "github.com/stretchr/testify/suite"
        )

        type APISuite struct {
            suite.Suite
            server *httptest.Server
            client *http.Client
        }

        func (s *APISuite) SetupSuite() {
            // 创建测试服务器
            mux := http.NewServeMux()
            mux.HandleFunc("/users", s.handleUsers)
            mux.HandleFunc("/users/", s.handleUserByID)

            s.server = httptest.NewServer(mux)
            s.client = &http.Client{}
        }

        func (s *APISuite) TearDownSuite() {
            s.server.Close()
        }

        func (s *APISuite) handleUsers(w http.ResponseWriter, r *http.Request) {
            switch r.Method {
            case "GET":
                json.NewEncoder(w).Encode([]User{
                    {ID: 1, Name: "Alice"},
                    {ID: 2, Name: "Bob"},
                })
            case "POST":
                w.WriteHeader(http.StatusCreated)
                json.NewEncoder(w).Encode(User{ID: 3, Name: "New"})
            }
        }

        func (s *APISuite) handleUserByID(w http.ResponseWriter, r *http.Request) {
            json.NewEncoder(w).Encode(User{ID: 1, Name: "Alice"})
        }

        func (s *APISuite) TestGetUsers() {
            resp, err := s.client.Get(s.server.URL + "/users")
            s.Require().NoError(err)
            defer resp.Body.Close()

            s.Equal(http.StatusOK, resp.StatusCode)

            var users []User
            err = json.NewDecoder(resp.Body).Decode(&users)
            s.NoError(err)
            s.Len(users, 2)
        }

        func (s *APISuite) TestCreateUser() {
            body := strings.NewReader(`{"name":"Charlie"}`)
            resp, err := s.client.Post(s.server.URL+"/users",
                "application/json", body)
            s.Require().NoError(err)
            defer resp.Body.Close()

            s.Equal(http.StatusCreated, resp.StatusCode)

            var user User
            err = json.NewDecoder(resp.Body).Decode(&user)
            s.NoError(err)
            s.Equal("New", user.Name)
        }

        func TestAPISuite(t *testing.T) {
            suite.Run(t, new(APISuite))
        }
        ---

7.4 Mock实战案例

01.Mock外部依赖
    a.Mock数据库
        创建数据库接口的Mock实现,控制查询返回的数据。可以测试成功和失败场景,无需真实数据库。Mock使测试快速且可重复。验证数据库方法被正确调用。
    b.Mock HTTP客户端
        Mock HTTP客户端避免真实网络请求。可以模拟各种响应如成功、超时、错误。测试客户端代码如何处理不同响应。Mock使测试不依赖外部服务。
    c.代码示例
        ---
        // Mock实战案例
        package mockpractice

        import (
            "errors"
            "testing"
            "github.com/stretchr/testify/mock"
            "github.com/stretchr/testify/suite"
        )

        // 数据库接口
        type UserRepository interface {
            FindByID(id int) (*User, error)
            Save(user *User) error
            Delete(id int) error
        }

        // Mock实现
        type MockUserRepository struct {
            mock.Mock
        }

        func (m *MockUserRepository) FindByID(id int) (*User, error) {
            args := m.Called(id)
            if args.Get(0) == nil {
                return nil, args.Error(1)
            }
            return args.Get(0).(*User), args.Error(1)
        }

        func (m *MockUserRepository) Save(user *User) error {
            args := m.Called(user)
            return args.Error(0)
        }

        func (m *MockUserRepository) Delete(id int) error {
            args := m.Called(id)
            return args.Error(0)
        }

        // 业务服务
        type UserService struct {
            repo UserRepository
        }

        func NewUserService(repo UserRepository) *UserService {
            return &UserService{repo: repo}
        }

        func (s *UserService) GetUser(id int) (*User, error) {
            return s.repo.FindByID(id)
        }

        func (s *UserService) UpdateUserName(id int, name string) error {
            user, err := s.repo.FindByID(id)
            if err != nil {
                return err
            }
            user.Name = name
            return s.repo.Save(user)
        }

        // 测试Suite
        type UserServiceSuite struct {
            suite.Suite
            mockRepo *MockUserRepository
            service  *UserService
        }

        func (s *UserServiceSuite) SetupTest() {
            s.mockRepo = new(MockUserRepository)
            s.service = NewUserService(s.mockRepo)
        }

        func (s *UserServiceSuite) TestGetUser_Success() {
            expectedUser := &User{ID: 1, Name: "Alice"}
            s.mockRepo.On("FindByID", 1).Return(expectedUser, nil)

            user, err := s.service.GetUser(1)

            s.NoError(err)
            s.Equal(expectedUser, user)
            s.mockRepo.AssertExpectations(s.T())
        }

        func (s *UserServiceSuite) TestGetUser_NotFound() {
            s.mockRepo.On("FindByID", 999).
                Return(nil, errors.New("not found"))

            user, err := s.service.GetUser(999)

            s.Error(err)
            s.Nil(user)
            s.mockRepo.AssertExpectations(s.T())
        }

        func (s *UserServiceSuite) TestUpdateUserName() {
            existingUser := &User{ID: 1, Name: "Old"}
            s.mockRepo.On("FindByID", 1).Return(existingUser, nil)
            s.mockRepo.On("Save", mock.MatchedBy(func(u *User) bool {
                return u.ID == 1 && u.Name == "New"
            })).Return(nil)

            err := s.service.UpdateUserName(1, "New")

            s.NoError(err)
            s.mockRepo.AssertExpectations(s.T())
        }

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

        // HTTP客户端Mock
        type HTTPClient interface {
            Get(url string) ([]byte, error)
            Post(url string, data []byte) ([]byte, error)
        }

        type MockHTTPClient struct {
            mock.Mock
        }

        func (m *MockHTTPClient) Get(url string) ([]byte, error) {
            args := m.Called(url)
            return args.Get(0).([]byte), args.Error(1)
        }

        func (m *MockHTTPClient) Post(url string, data []byte) ([]byte, error) {
            args := m.Called(url, data)
            return args.Get(0).([]byte), args.Error(1)
        }

        type APIService struct {
            client HTTPClient
        }

        func (s *APIService) FetchData(endpoint string) ([]byte, error) {
            return s.client.Get("https://api.example.com/" + endpoint)
        }

        func TestAPIService(t *testing.T) {
            mockClient := new(MockHTTPClient)
            service := &APIService{client: mockClient}

            expectedData := []byte(`{"status":"ok"}`)
            mockClient.On("Get", "https://api.example.com/data").
                Return(expectedData, nil)

            data, err := service.FetchData("data")

            assert := assert.New(t)
            assert.NoError(err)
            assert.Equal(expectedData, data)
            mockClient.AssertExpectations(t)
        }
        ---

7.5 测试重构技巧

01.消除重复代码
    a.提取辅助函数
        将重复的测试逻辑提取为辅助函数。辅助函数可以在同一测试文件或共享测试包中定义。使用t.Helper()标记辅助函数,错误报告更准确。减少重复提高测试可维护性。
    b.使用表格驱动
        用表格驱动测试替代多个相似的测试函数。定义测试用例数组,用循环执行。添加新用例只需添加数据。表格驱动使测试更简洁清晰。
    c.代码示例
        ---
        // 测试重构示例
        package refactor

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

        // ❌ 重构前:重复代码
        func TestValidateEmail_Valid(t *testing.T) {
            email := "[email protected]"
            result := ValidateEmail(email)
            assert.True(t, result)
        }

        func TestValidateEmail_Invalid1(t *testing.T) {
            email := "invalid"
            result := ValidateEmail(email)
            assert.False(t, result)
        }

        func TestValidateEmail_Invalid2(t *testing.T) {
            email := ""
            result := ValidateEmail(email)
            assert.False(t, result)
        }

        // ✅ 重构后:表格驱动
        func TestValidateEmail(t *testing.T) {
            tests := []struct {
                name     string
                email    string
                expected bool
            }{
                {"valid", "[email protected]", true},
                {"invalid no @", "invalid", false},
                {"empty", "", false},
            }

            for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
                    result := ValidateEmail(tt.email)
                    assert.Equal(t, tt.expected, result)
                })
            }
        }

        func ValidateEmail(email string) bool {
            return len(email) > 3
        }

        // ❌ 重构前:重复的断言
        func TestUser_Original(t *testing.T) {
            user := &User{ID: 1, Name: "Alice", Email: "[email protected]"}

            assert.NotNil(t, user)
            assert.Greater(t, user.ID, 0)
            assert.NotEmpty(t, user.Name)
            assert.Contains(t, user.Email, "@")
        }

        // ✅ 重构后:辅助函数
        func assertValidUser(t *testing.T, user *User) {
            t.Helper()

            assert.NotNil(t, user)
            assert.Greater(t, user.ID, 0)
            assert.NotEmpty(t, user.Name)
            assert.Contains(t, user.Email, "@")
        }

        func TestUser_Refactored(t *testing.T) {
            user := &User{ID: 1, Name: "Alice", Email: "[email protected]"}
            assertValidUser(t, user)
        }

        func TestMultipleUsers(t *testing.T) {
            users := []*User{
                {ID: 1, Name: "Alice", Email: "[email protected]"},
                {ID: 2, Name: "Bob", Email: "[email protected]"},
            }

            for _, user := range users {
                assertValidUser(t, user)
            }
        }
        ---

02.改善测试结构
    a.使用Suite组织
        将相关测试组织到Suite中,共享setup和teardown。Suite提供更好的测试组织结构。测试方法更清晰,依赖关系更明确。Suite适合复杂的测试场景。
    b.合理分组
        按功能或场景将测试分组到不同的Suite或子测试。每个Suite或测试方法关注一个明确的方面。避免巨大的测试方法,拆分成多个小测试。良好的组织使测试易于理解和维护。
    c.代码示例
        ---
        // 改善测试结构
        package structure

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

        // ❌ 重构前:混乱的测试
        func TestEverything(t *testing.T) {
            // 测试用户创建
            // ... 100行代码

            // 测试用户更新
            // ... 100行代码

            // 测试用户删除
            // ... 100行代码
        }

        // ✅ 重构后:使用Suite
        type UserCRUDSuite struct {
            suite.Suite
            service *UserService
        }

        func (s *UserCRUDSuite) SetupTest() {
            s.service = &UserService{}
        }

        func (s *UserCRUDSuite) TestCreate() {
            // 专注于创建逻辑
            user := s.service.CreateUser("Alice")
            s.NotNil(user)
        }

        func (s *UserCRUDSuite) TestUpdate() {
            // 专注于更新逻辑
            s.Run("update name", func() {
                // ...
            })

            s.Run("update email", func() {
                // ...
            })
        }

        func (s *UserCRUDSuite) TestDelete() {
            // 专注于删除逻辑
            err := s.service.DeleteUser(1)
            s.NoError(err)
        }

        func TestUserCRUDSuite(t *testing.T) {
            suite.Run(t, new(UserCRUDSuite))
        }

        type UserService struct{}

        func (s *UserService) CreateUser(name string) *User {
            return &User{Name: name}
        }

        func (s *UserService) DeleteUser(id int) error {
            return nil
        }
        ---

7.6 性能测试

01.基准测试编写
    a.基准测试函数
        基准测试函数以Benchmark开头,接收*testing.B。在b.N循环中执行被测代码。Go会自动调整N使测试运行足够长时间得到稳定结果。使用b.ResetTimer()排除setup时间。
    b.性能分析
        使用go test -bench运行基准测试。添加-benchmem显示内存分配统计。使用-cpuprofile生成CPU profile文件。结合pprof工具深入分析性能瓶颈。
    c.代码示例
        ---
        // 性能测试示例
        package performance

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

        func StringConcat(strs []string) string {
            result := ""
            for _, s := range strs {
                result += s
            }
            return result
        }

        func StringConcatBuilder(strs []string) string {
            var builder strings.Builder
            for _, s := range strs {
                builder.WriteString(s)
            }
            return builder.String()
        }

        // 基准测试
        func BenchmarkStringConcat(b *testing.B) {
            strs := []string{"hello", "world", "test"}

            b.ResetTimer()
            for i := 0; i < b.N; i++ {
                StringConcat(strs)
            }
        }

        func BenchmarkStringConcatBuilder(b *testing.B) {
            strs := []string{"hello", "world", "test"}

            b.ResetTimer()
            for i := 0; i < b.N; i++ {
                StringConcatBuilder(strs)
            }
        }

        // 不同输入大小的基准测试
        func BenchmarkStringConcat_Small(b *testing.B) {
            strs := []string{"a", "b", "c"}
            for i := 0; i < b.N; i++ {
                StringConcat(strs)
            }
        }

        func BenchmarkStringConcat_Large(b *testing.B) {
            strs := make([]string, 100)
            for i := range strs {
                strs[i] = "test"
            }

            b.ResetTimer()
            for i := 0; i < b.N; i++ {
                StringConcat(strs)
            }
        }

        // 运行命令:
        // go test -bench=. -benchmem
        // go test -bench=StringConcat -benchtime=5s
        // go test -bench=. -cpuprofile=cpu.prof

        // 正确性验证(配合基准测试)
        func TestStringConcatCorrectness(t *testing.T) {
            strs := []string{"hello", "world"}
            result := StringConcat(strs)
            assert.Equal(t, "helloworld", result)
        }
        ---

02.性能对比测试
    a.对比不同实现
        为同一功能的不同实现编写基准测试。对比它们的性能差异。帮助选择最优实现。记录性能基线,防止性能退化。
    b.性能回归检测
        定期运行基准测试,对比历史数据。显著的性能变化需要调查。可以在CI中运行基准测试。使用benchstat等工具对比结果。
    c.代码示例
        ---
        // 性能对比测试
        package comparison

        import (
            "testing"
        )

        // 实现1:使用map
        func ContainsMap(items []int, target int) bool {
            m := make(map[int]bool)
            for _, item := range items {
                m[item] = true
            }
            return m[target]
        }

        // 实现2:线性查找
        func ContainsLinear(items []int, target int) bool {
            for _, item := range items {
                if item == target {
                    return true
                }
            }
            return false
        }

        func BenchmarkContainsMap(b *testing.B) {
            items := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
            for i := 0; i < b.N; i++ {
                ContainsMap(items, 5)
            }
        }

        func BenchmarkContainsLinear(b *testing.B) {
            items := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
            for i := 0; i < b.N; i++ {
                ContainsLinear(items, 5)
            }
        }

        // 大数据集对比
        func BenchmarkContainsMap_Large(b *testing.B) {
            items := make([]int, 10000)
            for i := range items {
                items[i] = i
            }

            b.ResetTimer()
            for i := 0; i < b.N; i++ {
                ContainsMap(items, 5000)
            }
        }

        func BenchmarkContainsLinear_Large(b *testing.B) {
            items := make([]int, 10000)
            for i := range items {
                items[i] = i
            }

            b.ResetTimer()
            for i := 0; i < b.N; i++ {
                ContainsLinear(items, 5000)
            }
        }
        ---

7.7 常见问题

01.断言失败定位
    a.使用子测试
        使用t.Run创建子测试,每个子测试有独立的名称。测试失败时会显示完整的测试路径。便于快速定位失败的具体用例。子测试使输出更有组织性。
    b.添加有意义的消息
        在断言中添加描述性消息。消息应该说明测试的意图和失败的原因。帮助快速理解失败的上下文。特别是在表格驱动测试中很有用。
    c.代码示例
        ---
        // 断言失败定位
        package debugging

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

        // ❌ 难以定位的测试
        func TestPoorLocation(t *testing.T) {
            assert.Equal(t, 5, Add(2, 3))
            assert.Equal(t, 10, Add(5, 5))
            assert.Equal(t, 0, Add(0, 0))
            // 如果第二个失败,不容易看出是哪个
        }

        // ✅ 易于定位:使用子测试
        func TestGoodLocation(t *testing.T) {
            t.Run("2+3=5", func(t *testing.T) {
                assert.Equal(t, 5, Add(2, 3))
            })

            t.Run("5+5=10", func(t *testing.T) {
                assert.Equal(t, 10, Add(5, 5))
            })

            t.Run("0+0=0", func(t *testing.T) {
                assert.Equal(t, 0, Add(0, 0))
            })
        }

        // ✅ 添加有意义的消息
        func TestWithMessages(t *testing.T) {
            tests := []struct {
                name     string
                a, b     int
                expected int
            }{
                {"positive", 2, 3, 5},
                {"negative", -2, -3, -5},
            }

            for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
                    result := Add(tt.a, tt.b)
                    assert.Equal(t, tt.expected, result,
                        "Add(%d, %d) should equal %d but got %d",
                        tt.a, tt.b, tt.expected, result)
                })
            }
        }

        func Add(a, b int) int {
            return a + b
        }
        ---

02.Mock调试
    a.验证Mock调用
        使用AssertExpectations确保Mock被正确调用。使用AssertCalled验证特定调用。使用AssertNumberOfCalls验证调用次数。Mock未按预期调用时会有清晰的错误消息。
    b.Mock行为问题
        确保On设置的参数匹配实际调用的参数。使用mock.Anything作为占位符。检查Return的返回值类型是否正确。使用Run方法打印调试信息。
    c.代码示例
        ---
        // Mock调试
        package mockdebug

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

        type Service interface {
            Process(data string) (string, error)
        }

        type MockService struct {
            mock.Mock
        }

        func (m *MockService) Process(data string) (string, error) {
            args := m.Called(data)
            return args.String(0), args.Error(1)
        }

        // ❌ 常见错误:参数不匹配
        func TestMockError(t *testing.T) {
            mockSvc := new(MockService)

            // 设置期望:"test"
            mockSvc.On("Process", "test").Return("result", nil)

            // 但实际调用:"TEST"(不匹配)
            // result, _ := mockSvc.Process("TEST") // 会失败

            // ✅ 正确:参数匹配
            result, err := mockSvc.Process("test")
            assert.NoError(t, err)
            assert.Equal(t, "result", result)

            mockSvc.AssertExpectations(t)
        }

        // ✅ 使用Anything匹配任意参数
        func TestMockAnything(t *testing.T) {
            mockSvc := new(MockService)

            mockSvc.On("Process", mock.Anything).Return("result", nil)

            // 任何参数都匹配
            mockSvc.Process("test1")
            mockSvc.Process("test2")

            mockSvc.AssertExpectations(t)
        }

        // ✅ 调试:使用Run打印信息
        func TestMockDebug(t *testing.T) {
            mockSvc := new(MockService)

            mockSvc.On("Process", mock.Anything).
                Run(func(args mock.Arguments) {
                    fmt.Printf("Process called with: %v\n", args.Get(0))
                }).
                Return("result", nil)

            mockSvc.Process("debug test")

            mockSvc.AssertExpectations(t)
        }
        ---

03.测试超时
    a.设置测试超时
        使用testing.Short()跳过慢速测试。在测试中使用context.WithTimeout控制操作超时。CI环境可以设置全局超时。避免测试无限hang住。
    b.处理慢速测试
        识别慢速测试并优化。考虑使用Mock替代真实操作。将慢速集成测试与快速单元测试分离。使用build tags标记慢速测试。
    c.代码示例
        ---
        // 测试超时处理
        package timeout

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

        // 跳过慢速测试
        func TestSlowOperation(t *testing.T) {
            if testing.Short() {
                t.Skip("skipping slow test in short mode")
            }

            // 慢速操作
            time.Sleep(1 * time.Second)
        }

        // 使用超时控制
        func TestWithTimeout(t *testing.T) {
            ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
            defer cancel()

            done := make(chan bool)
            go func() {
                // 模拟操作
                time.Sleep(50 * time.Millisecond)
                done <- true
            }()

            select {
            case <-done:
                assert.True(t, true)
            case <-ctx.Done():
                t.Fatal("operation timed out")
            }
        }

        // 运行命令:
        // go test -short  (跳过慢速测试)
        // go test -timeout 30s (设置全局超时)
        ---

7.8 调试技巧

01.使用t.Log输出
    a.日志记录
        使用t.Log或t.Logf在测试中输出调试信息。日志只在测试失败或使用-v标志时显示。帮助理解测试执行过程。记录中间状态和变量值。
    b.条件日志
        使用t.Failed()检查测试是否已失败。在失败时输出额外的调试信息。避免在成功时输出大量日志。保持测试输出简洁。
    c.代码示例
        ---
        // 测试调试技巧
        package debug

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

        func ComplexOperation(input int) int {
            // 复杂的多步骤操作
            step1 := input * 2
            step2 := step1 + 10
            step3 := step2 / 2
            return step3
        }

        func TestComplexOperation(t *testing.T) {
            input := 5
            expected := 10

            // 记录输入
            t.Logf("Testing with input: %d", input)

            result := ComplexOperation(input)

            // 记录结果
            t.Logf("Got result: %d", result)

            if !assert.Equal(t, expected, result) {
                // 失败时记录更多信息
                t.Logf("Expected %d but got %d", expected, result)
            }
        }

        // 详细的调试日志
        func TestWithDetailedLogging(t *testing.T) {
            data := []int{1, 2, 3, 4, 5}

            t.Log("Processing data:", data)

            sum := 0
            for i, v := range data {
                sum += v
                t.Logf("Step %d: added %d, sum is now %d", i, v, sum)
            }

            assert.Equal(t, 15, sum)

            if t.Failed() {
                t.Log("Test failed, final sum:", sum)
            }
        }

        // 运行:go test -v  (显示所有日志)
        ---

02.断点调试
    a.IDE调试器
        在IDE中设置断点,以调试模式运行测试。逐步执行代码,检查变量值。VSCode和GoLand都支持测试调试。比日志更直观地理解代码执行。
    b.Delve调试器
        使用dlv test命令行调试器。在代码中插入断点。检查goroutine状态。调试并发问题时特别有用。
    c.代码示例
        ---
        // 调试器使用示例
        package debugger

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

        func ProcessData(data []int) []int {
            result := make([]int, 0)
            for _, v := range data {
                // 在这里设置断点
                if v > 0 {
                    result = append(result, v*2)
                }
            }
            return result
        }

        func TestProcessData(t *testing.T) {
            input := []int{-1, 2, -3, 4, 5}

            // 在这里设置断点,检查input
            result := ProcessData(input)

            // 在这里设置断点,检查result
            expected := []int{4, 8, 10}
            assert.Equal(t, expected, result)
        }

        // VSCode调试:
        // 1. 点击行号左侧设置断点
        // 2. 点击测试函数上方的"debug test"

        // Delve命令行调试:
        // $ dlv test
        // (dlv) break ProcessData
        // (dlv) continue
        // (dlv) print v
        // (dlv) next
        ---

03.表格驱动测试调试
    a.隔离失败用例
        当表格测试中某个用例失败时,使用-run标志只运行该用例。修改测试临时注释其他用例。使用t.Run的名称过滤。快速迭代修复问题。
    b.添加调试用例
        在表格中添加临时调试用例。使用极简输入定位问题。逐步接近实际失败的情况。找到根因后移除调试用例。
    c.代码示例
        ---
        // 表格驱动测试调试
        package tabledebug

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

        func Divide(a, b int) (int, error) {
            if b == 0 {
                return 0, assert.AnError
            }
            return a / b, nil
        }

        func TestDivide(t *testing.T) {
            tests := []struct {
                name        string
                a, b        int
                expected    int
                expectError bool
            }{
                {"normal", 10, 2, 5, false},
                {"zero divisor", 10, 0, 0, true},
                {"negative", -10, 2, -5, false},
                // 临时调试用例
                {"debug case", 7, 2, 3, false}, // 期望3但实际是3.5
            }

            for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
                    // 添加调试日志
                    t.Logf("Testing: %d / %d", tt.a, tt.b)

                    result, err := Divide(tt.a, tt.b)

                    // 详细日志
                    t.Logf("Result: %d, Error: %v", result, err)

                    if tt.expectError {
                        assert.Error(t, err)
                    } else {
                        assert.NoError(t, err)
                        assert.Equal(t, tt.expected, result)
                    }
                })
            }
        }

        // 只运行特定用例:
        // go test -v -run TestDivide/normal
        // go test -v -run TestDivide/debug

        // 调试技巧:
        // 1. 添加t.Log输出中间值
        // 2. 使用断点检查变量
        // 3. 简化测试用例到最小复现
        // 4. 逐步添加复杂度直到找到问题
        ---

04.并发测试调试
    a.Race检测
        始终使用-race运行并发测试。race detector会报告数据竞争的详细信息。修复所有报告的竞态条件。race检测是发现并发bug的第一道防线。
    b.减少并发度
        调试时减少goroutine数量。从2-3个goroutine开始。逐步增加找到问题阈值。使用channel或日志同步输出。
    c.代码示例
        ---
        // 并发测试调试
        package concurrentdebug

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

        // ❌ 有bug的并发代码
        type UnsafeCounter struct {
            count int
        }

        func (c *UnsafeCounter) Inc() {
            c.count++ // data race
        }

        func (c *UnsafeCounter) Value() int {
            return c.count // data race
        }

        func TestUnsafeCounter(t *testing.T) {
            counter := &UnsafeCounter{}

            // 调试:先用少量goroutine
            var wg sync.WaitGroup
            for i := 0; i < 10; i++ { // 从10个开始而不是1000个
                wg.Add(1)
                go func(n int) {
                    defer wg.Done()
                    t.Logf("Goroutine %d incrementing", n)
                    counter.Inc()
                }(i)
            }

            wg.Wait()

            result := counter.Value()
            t.Logf("Final value: %d", result)
            assert.Equal(t, 10, result)
        }

        // 运行:go test -race -v
        // 会检测到data race并显示详细位置

        // ✅ 修复后的版本
        type SafeCounter struct {
            mu    sync.Mutex
            count int
        }

        func (c *SafeCounter) Inc() {
            c.mu.Lock()
            c.count++
            c.mu.Unlock()
        }

        func (c *SafeCounter) Value() int {
            c.mu.Lock()
            defer c.mu.Unlock()
            return c.count
        }

        func TestSafeCounter(t *testing.T) {
            counter := &SafeCounter{}
            var wg sync.WaitGroup

            for i := 0; i < 100; i++ {
                wg.Add(1)
                go func() {
                    defer wg.Done()
                    counter.Inc()
                }()
            }

            wg.Wait()
            assert.Equal(t, 100, counter.Value())
        }

        // 调试步骤:
        // 1. go test -race 检测竞态
        // 2. 减少goroutine数量定位问题
        // 3. 添加日志追踪执行顺序
        // 4. 使用sync.Mutex等同步原语修复
        // 5. 再次运行-race验证修复
        ---