单元测试(go)

项目demo地址:go-test

目前只描述了简单的方法,文档持续更新中...

本文主要针对golang语言的单元测试工具,博客内容也会涉及一些单元测试相关的内容

**什么是单元测试:**单元测试是软件测试体系中最基础、最核心的测试类型,它聚焦于对软件系统中最小的 "可测试单元" 进行独立验证,确保该单元的功能符合预期设计。

**简单描述下前因后果:**工作需要对项目代码系统化执行单元测试,要求覆盖率达到95%以上,因为不同人的开发风格和代码习惯,外加项目框架和架构的一些要求。单元测试,这个东西一般情况都会让人很痛苦,至于因为啥,我相信看到我这篇博客的各位,都是有不同程度的感同身受的,我这里介绍三种单测工具,基础的单元测试书写和使用就不过多赘述了。

一、单元测试的核心方式

注意:这块作为扩展,如需直接了解工具可忽略这部分

单元测试的实现方式可从核心分类维度展开,结合具体落地实践和技术选型,不同方式适用于不同场景和项目规模。

1、按测试编写时机划分(核心流程维度)

从开发流程角度区分的两种核心方式,决定了单元测试与业务代码的协作关系。

1.后写单元测试(传统方式,最常用)
  1. 核心定义:先编写业务功能代码,待功能实现完成后,再针对性补写对应的单元测试用例,验证已实现的代码逻辑是否符合预期。
  2. 适用场景:大部分传统开发场景、快速迭代的小型需求、开发者对 TDD 模式不熟悉的项目。
  3. 优势:符合开发者 "先实现功能再验证" 的直觉,上手成本低,无需提前设计详细的测试用例。
  4. 劣势:可能遗漏部分边界场景的测试,且容易因业务代码耦合度高,导致测试难以编写(后期补测时,修改代码解耦的成本更高)。
2.测试驱动开发(TDD,Test-Driven Development,进阶方式)
  1. 核心定义 :遵循 "先写测试,再写业务代码,最后重构" 的循环流程,测试用例先定义好被测单元的预期行为(输入、输出、异常场景),再编写满足测试用例的业务代码,最终优化代码结构。
  2. 核心流程(红 - 绿 - 重构循环)
    1. 红(Red):编写一个失败的测试用例(此时业务代码未实现,测试必然失败);
    2. 绿(Green):编写最少的业务代码,仅满足让该测试用例通过(不追求代码优雅,只保证功能达标);
    3. 重构(Refactor):在测试用例保驾护航的前提下,优化业务代码的结构、可读性、性能等,确保重构后测试用例仍能通过。
  3. 适用场景:对代码质量要求高的核心模块、复杂业务逻辑、需要长期维护的大型项目。
  4. 优势
    • 强制开发者提前梳理需求和接口设计,减少后期需求偏差;
    • 测试用例覆盖率更高,天然覆盖正常、边界、异常场景;
    • 重构无风险,测试用例作为 "安全网",确保重构不破坏原有功能;
    • 代码耦合度更低,因为先写测试会倒逼开发者设计可测试的代码(如依赖接口而非具体实现)。

2、按依赖处理方式划分(技术实现维度)

这是单元测试落地的核心技术维度,决定了如何隔离外部依赖,保证测试的独立性。

1. 基于 Mock/Stub 的单元测试(主流方式)
  • 核心定义 :当被测单元依赖外部资源(数据库、RPC 服务、HTTP 接口、文件系统等)时,通过 ** 模拟(Mock)桩(Stub)** 实现替代真实依赖,预设返回值或行为,从而脱离外部环境限制,专注测试业务逻辑。

  • Mock vs Stub 区别(通俗理解)

    类型 核心特征 适用场景
    Stub 仅预设固定返回值,无行为验证 只需依赖返回值完成业务逻辑测试
    Mock 不仅预设返回值,还可验证依赖方法是否被调用、调用次数、参数是否正确 需要验证业务逻辑对依赖的调用行为
  • 实现方式

    1. 手动编写 Mock/Stub(简单场景):如之前 Go 示例中,手动实现UserDB接口的MockUserDB,预设返回值;
    2. 工具自动生成 Mock(复杂场景):Go 生态的gomock+mockgen、Java 生态的Mockito、Python 生态的unittest.mock,可根据接口自动生成 Mock 代码,支持更灵活的行为验证。
  • 优势:测试独立、快速、可复现,不受外部资源状态影响,能覆盖各种异常场景(如依赖服务报错、超时)。

  • Go 语言工具示例(gomock)

    (1)先安装工具:

    复制代码
    go get github.com/golang/mock/gomock
    
    go install github.com/golang/mock/mockgen@latest

    (2)自动生成 Mock 代码,无需手动编写,支持验证调用行为。

2. 真实依赖单元测试(小众场景)
  • 核心定义:不使用模拟实现,直接使用真实的外部依赖(如真实数据库、真实 HTTP 服务)进行单元测试,验证被测单元与真实依赖的协作是否正常。
  • 适用场景:依赖逻辑简单、真实依赖易于搭建和控制(如本地轻量数据库 SQLite)、对依赖协作正确性要求极高的场景。
  • 优势:测试结果更贴近生产环境,能发现与真实依赖协作的潜在问题(如 SQL 语法错误、接口参数不匹配)。
  • 劣势
    • 测试执行速度慢,依赖外部资源启动和初始化;
    • 测试结果不稳定,受外部资源状态影响(如数据库数据被修改导致测试失败);
    • 测试环境搭建复杂,需要统一管理依赖配置(如数据库连接信息、服务地址)。
  • 示例:测试数据库查询函数时,直接连接本地测试 MySQL 数据库,预先插入测试数据,执行查询后验证结果,最后清理测试数据。

3、个人理解

上面的描述是大模型系统化生成的内容,下面是博主自行整理的,至于为什么会有这样一段赘述,是和下面的工具有些关联

单元测试两种开发方式:
1.先开发业务代码,后写单元测试代码(常用)
  1. interface单元测试

    1. 核心优势:

      • 完全解耦外部依赖,实现 "纯净" 测试
      • 灵活覆盖全量业务场景,无测试死角
      • 测试执行效率极高,支持高频执行
      • 为代码重构提供 "安全网",降低重构风险
      • 倒逼良好的代码设计,提升代码质量
      • 可验证依赖方法的调用行为(进阶优势)
    2. 缺点:

      • 增加前期开发成本,引入额外代码量
      • 存在 "过度抽象" 的风险
      • 无法验证真实依赖的协作正确性
      • Mock 与真实实现可能存在 "行为不一致"
      • 对简单场景 "杀鸡用牛刀",性价比不高
    3. 总结:

      如果代码开发的时候考虑到需要进行单元测试功能开发,可以直接在业务功能开发时进行单元测试的预先埋点处理,做好接口的开发,不过一般情况下大家的开发习惯都不会考虑单元测试这种情况,这时候在想要回去处理单元测试,interface这种方式就会极为麻烦和笨重,单测时间成本成指数级增长。

  2. 使用单元测试工具

    1. 内置核心工具:testing包(基础基石)

    2. 工具包(具体使用方法和功能下面介绍)

      • 接口测试工具:httptest
      • 数据库测试工具:go-sqlmock
      • 打桩测试工具:gomonkey
    3. 优点:

      在业务逻辑代码开发完成后几乎可以不调整原始逻辑代码进行单元测试代码开发

2.先开发单元测试代码,后写逻辑代码(很少见,不介绍)

二、单元测试工具

主要介绍三个工具:httptest、go-sqlmock、gomonkey

1、httptest

**介绍:**Go 内置标准库net/http/httptest,核心用途用于测试net/http构建的HTTP服务(如API接口、Web服务等),它可以模拟HTTP请求发送和HTTP响应的接收,无需启动真实的HTTP服务器即可完成接口测试,极大提升了测试的便捷性和执行效率

优点:

  • 无需启动真实服务器:无需调用 http.ListenAndServe 启动端口监听,直接测试 HTTP 处理器(Handler/HandlerFunc),测试执行更高效。
  • 脱离网络依赖:模拟 HTTP 请求与响应的完整生命周期,不受网络波动、端口占用等外部因素影响,测试结果稳定可复现。
  • 精准捕获响应细节:可完整获取响应状态码、响应头、响应体等所有信息,便于精准断言验证。
  • 支持两种核心测试场景:
    • 测试 HTTP 处理器(直接调用 ServeHTTP,最常用)
    • 启动临时测试服务器(模拟真实服务端,用于客户端测试或集成测试)
1.安装

内置工具可以直接使用

2.使用示例

相关代码在gitee代码仓库的示例代码中,仓库地址请看博客开头

blog.go

go 复制代码
package blog

import (
	"errors"
	"fmt"
	"io"
	"net/http"
)

func SearchHttp(targetURL string) (interface{}, error) {
	resp, err := http.Get(targetURL)
	if err != nil {
		errMsg := fmt.Sprintf("发送 GET 请求失败:%v", err)
		fmt.Println(errMsg)
		return nil, errors.New(errMsg)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		errMsg := fmt.Sprintf("请求失败,状态码:%d,状态信息:%s", resp.StatusCode, resp.Status)
		fmt.Println(errMsg)
		return nil, errors.New(errMsg)
	}

	bodyBytes, _ := io.ReadAll(resp.Body)
	return string(bodyBytes), nil
}

blog_test.go

go 复制代码
package blog

import (
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
)

func TestSearchHttp(t *testing.T) {
	// --------------- 1. 定义所有测试场景的表驱动用例(循环遍历执行) ---------------
	testCases := []struct {
		name             string        // 用例名称
		prepareFunc      func() string // 前置准备:创建模拟服务器/构造URL,返回待请求的URL
		expectedErr      bool          // 是否预期返回错误
		errContains      string        // 预期错误信息包含的关键字(非空则验证)
		expectedNonEmpty bool          // 正常场景下,是否预期返回非空字符串
	}{
		// 场景1:正常请求(200 OK,响应体正常)
		{
			name: "正常请求-状态码200",
			prepareFunc: func() string {
				// 启动模拟HTTP服务器,返回200和测试响应体
				mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
					mockBody := "<!DOCTYPE html><html><title>百度一下</title></html>"
					w.WriteHeader(http.StatusOK)
					_, _ = w.Write([]byte(mockBody))
				}))
				// 关键:将mockServer放入测试上下文,确保后续能关闭(避免资源泄露)
				t.Cleanup(func() { mockServer.Close() })
				return mockServer.URL
			},
			expectedErr:      false,
			errContains:      "",
			expectedNonEmpty: true,
		},
		// 场景2:请求失败(无效URL,模拟网络异常)
		{
			name: "异常场景-无效URL请求失败",
			prepareFunc: func() string {
				// 返回一个无效的URL,触发http.Get请求失败
				return "http://invalid-xxx-url-12345/"
			},
			expectedErr:      true,
			errContains:      "发送 GET 请求失败",
			expectedNonEmpty: false,
		},
		// 场景3:状态码非200(模拟404 Not Found)
		{
			name: "异常场景-状态码404",
			prepareFunc: func() string {
				// 启动模拟HTTP服务器,返回404状态码
				mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
					w.WriteHeader(http.StatusNotFound)
					_, _ = w.Write([]byte("页面不存在"))
				}))
				t.Cleanup(func() { mockServer.Close() })
				return mockServer.URL
			},
			expectedErr:      true,
			errContains:      "请求失败,状态码:404",
			expectedNonEmpty: false,
		},
		// 场景4:状态码非200(模拟500服务器内部错误)
		{
			name: "异常场景-状态码500",
			prepareFunc: func() string {
				// 启动模拟HTTP服务器,返回500状态码
				mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
					w.WriteHeader(http.StatusInternalServerError)
					_, _ = w.Write([]byte("服务器内部错误"))
				}))
				t.Cleanup(func() { mockServer.Close() })
				return mockServer.URL
			},
			expectedErr:      true,
			errContains:      "请求失败,状态码:500",
			expectedNonEmpty: false,
		},
	}

	// --------------- 2. 循环遍历所有测试用例,统一执行验证 ---------------
	for _, tc := range testCases {
		// 循环内使用t.Run创建子用例(便于精准定位失败场景,不影响其他用例)
		t.Run(tc.name, func(t *testing.T) {
			// 步骤1:执行前置准备,获取待请求的URL
			targetURL := tc.prepareFunc()

			// 步骤2:调用被测函数
			result, err := SearchHttp(targetURL)

			// 步骤3:统一断言验证
			// 3.1 验证错误是否符合预期
			if (err != nil) != tc.expectedErr {
				t.Fatalf("错误预期不符:预期是否错误[%t],实际是否错误[%t],错误信息[%v]",
					tc.expectedErr, err != nil, err)
			}

			// 3.2 若预期错误,验证错误信息是否包含指定关键字
			if tc.expectedErr && tc.errContains != "" {
				if !strings.Contains(err.Error(), tc.errContains) {
					t.Errorf("错误信息不符:预期包含[%s],实际错误[%v]", tc.errContains, err)
				}
			}

			// 3.3 验证返回值是否符合预期
			if tc.expectedErr {
				// 异常场景:预期返回nil
				if result != nil {
					t.Errorf("异常场景预期返回nil,实际返回[%v],类型[%T]", result, result)
				}
			} else {
				// 正常场景:验证返回值是string类型,且非空(若预期非空)
				resultStr, ok := result.(string)
				if !ok {
					t.Fatalf("正常场景预期返回string类型,实际返回[%T]", result)
				}
				if tc.expectedNonEmpty && len(resultStr) == 0 {
					t.Error("正常场景预期返回非空字符串,实际返回空字符串")
				}
			}
		})
	}
}

命令行执行命令

go test -cover

结果:

powershell 复制代码
PS D:\wyl\workspace\go\tracer\dao\blog> go test -cover
发送 GET 请求失败:Get "http://invalid-xxx-url-12345/": dial tcp: lookup invalid-xxx-url-12345: no such host
请求失败,状态码:404,状态信息:404 Not Found
请求失败,状态码:500,状态信息:500 Internal Server Error
PASS
coverage: 100.0% of statements
ok      tracer/dao/blog 1.695s
PS D:\wyl\workspace\go\tracer\dao\blog>

2、go-sqlmock

介绍:gosqlmock是一个用于模拟数据库 /sql 驱动的库,核心作用是在不依赖真实数据库实例的情况下,对数据库相关逻辑进行单元测试,避免测试过程中操作真实数据、产生脏数据或依赖数据库服务可用性。

优点:

  • 解除真实数据库依赖,保证测试独立、稳定、无脏数据
  • 精准控制数据库行为,覆盖常规 / 异常全量测试场景
  • 兼容 database/sql 标准库和主流 ORM,无侵入式集成
  • 严格验证预期行为,提升测试准确性,发现隐藏问题
  • 轻量级无冗余,内存级执行,测试性能优异
  • 支持正则匹配,灵活适配复杂 SQL 场景
1.安装
复制代码
go get github.com/DATA-DOG/go-sqlmock
2.使用示例

相关代码在gitee代码仓库的示例代码中,仓库地址请看博客开头

price_policy.go

go 复制代码
package price

import (
	"gorm.io/gorm"
	"tracer/model"
)

type PricePolicy struct {
	gorm.Model
	Catogory      string `gorm:"type:varchar(64)" json:"catogory" label:"收费类型"`
	Title         string `gorm:"type:varchar(64)" json:"title" label:"标题"`
	Price         uint64 `gorm:"type:int(5)" json:"price" label:"价格"`
	ProjectNum    uint64 `json:"project_num" label:"项目数量"`
	ProjectMember uint64 `json:"project_member" label:"项目成员人数"`
	ProjectSpace  uint64 `json:"project_space" label:"每个项目空间" help_text:"单位是M"`
	PerFileSize   uint64 `json:"per_file_size" label:"单文件大小" help_text:"单位是M"`
}

// GetAllBlog 查询所有博客信息
func GetAllBlog() PricePolicy {
	var allBlog PricePolicy
	model.DB.Find(&allBlog)
	return allBlog
}

// TypeBlog 根据类型查找博客
func TypeBlog(tyb string) PricePolicy {
	var typeBlog PricePolicy
	model.DB.Where("type=?", tyb).Find(&typeBlog)
	return typeBlog
}

// TopBlog 置顶博客查询
func TopBlog(top string) PricePolicy {
	var topBlog PricePolicy
	model.DB.Where("top=?", top).Find(&topBlog)
	return topBlog
}

price_policy_test.go

go 复制代码
package price

import (
	"github.com/DATA-DOG/go-sqlmock"
	"github.com/stretchr/testify/assert"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"testing"
	"time"
	"tracer/model"
)

// TestGetAllBlog GetAllBlog 函数单元测试
func TestGetAllBlog(t *testing.T) {
	// 步骤1:创建 sqlmock 模拟连接(内存级,无真实数据库依赖)
	// sqlmock.New() 返回 mockDB(*sql.DB)、mock(sqlmock.Sqlmock)、error
	mockSqlDB, mock, err := sqlmock.New()
	assert.NoError(t, err, "创建 sqlmock 连接失败")
	defer mockSqlDB.Close() // 测试结束关闭模拟连接

	// 步骤2:将 sqlmock 连接适配为 GORM 可用的 DB 实例
	// 关键:使用 gorm mysql 驱动,传入 mock 的 *sql.DB 实例
	gormDB, err := gorm.Open(mysql.New(mysql.Config{
		Conn:                      mockSqlDB, // 绑定 sqlmock 的连接
		SkipInitializeWithVersion: true,      // 跳过 MySQL 版本检测(模拟连接无需版本信息)
	}), &gorm.Config{})
	assert.NoError(t, err, "GORM 绑定 sqlmock 连接失败")

	// 步骤3:替换全局 DB 为 mock 的 GORM DB(核心:让业务函数使用 mock 连接)
	model.DB = gormDB

	// 步骤4:构造模拟返回数据(与 PricePolicy 字段对应,需包含 gorm.Model 的默认字段)
	expectedPolicy := PricePolicy{
		Model: gorm.Model{
			ID:        1,
			CreatedAt: time.Time{}, // 测试中可忽略时间字段,若需精确匹配可赋值 time.Time 实例
			UpdatedAt: time.Time{},
			DeletedAt: gorm.DeletedAt{},
		},
		Catogory:      "个人版",
		Title:         "基础收费套餐",
		Price:         99,
		ProjectNum:    5,
		ProjectMember: 10,
		ProjectSpace:  1024,
		PerFileSize:   50,
	}

	// 步骤5:设置 sqlmock 预期(关键:匹配 GORM 自动生成的 SQL 语句)
	// GORM 的 Find(&allBlog) 会生成 SELECT * FROM `price_policies` 语句(表名默认是结构体小写复数)
	// 使用正则匹配,忽略无关空格和潜在的字段顺序差异
	rows := sqlmock.NewRows([]string{
		"id", "created_at", "updated_at", "deleted_at",
		"catogory", "title", "price", "project_num",
		"project_member", "project_space", "per_file_size",
	}).AddRow(
		expectedPolicy.ID, expectedPolicy.CreatedAt, expectedPolicy.UpdatedAt, expectedPolicy.DeletedAt,
		expectedPolicy.Catogory, expectedPolicy.Title, expectedPolicy.Price, expectedPolicy.ProjectNum,
		expectedPolicy.ProjectMember, expectedPolicy.ProjectSpace, expectedPolicy.PerFileSize,
	)

	// 预设查询预期:匹配 GORM 生成的 SELECT 语句
	mock.ExpectQuery("^SELECT \\* FROM `price_policies`").
		WillReturnRows(rows) // 设置查询返回的模拟数据

	// 步骤6:执行待测试函数
	actualPolicy := GetAllBlog()

	// 步骤7:验证结果
	// 验证核心字段是否一致(gorm.Model 时间字段若未赋值,可忽略或单独校验)
	assert.Equal(t, expectedPolicy.ID, actualPolicy.ID, "ID 不匹配")
	assert.Equal(t, expectedPolicy.Catogory, actualPolicy.Catogory, "收费类型不匹配")
	assert.Equal(t, expectedPolicy.Title, actualPolicy.Title, "标题不匹配")
	assert.Equal(t, expectedPolicy.Price, actualPolicy.Price, "价格不匹配")
	assert.Equal(t, expectedPolicy.ProjectNum, actualPolicy.ProjectNum, "项目数量不匹配")
	assert.Equal(t, expectedPolicy.ProjectMember, actualPolicy.ProjectMember, "项目成员人数不匹配")
	assert.Equal(t, expectedPolicy.ProjectSpace, actualPolicy.ProjectSpace, "项目空间不匹配")
	assert.Equal(t, expectedPolicy.PerFileSize, actualPolicy.PerFileSize, "单文件大小不匹配")

	// 关键:验证所有 sqlmock 预期都已被执行(无遗漏、无多余操作)
	assert.NoError(t, mock.ExpectationsWereMet(), "存在未满足的 sqlmock 预期")
}

命令行执行命令

go test -cover

结果:

powershell 复制代码
PS D:\wyl\workspace\go\tracer\model\price> go test -cover
PASS
coverage: 33.3% of statements
ok      tracer/model/price      0.076s

3、gomonkey

介绍:gomonkey是一款强大的运行时打桩(Mock)工具,能够在不修改源代码的前提下,对函数、方法、全局变量等进行动态替换,广泛用于单元测试场景

优点:

  • 无侵入式打桩,无需修改业务代码
  • 功能全面,支持函数、方法、全局变量等多种打桩场景
  • 支持私有成员打桩,适配遗留项目
  • 轻量级易用,API 简洁,兼容主流框架
  • 灵活控制打桩生命周期,精准适配测试需求
1.安装
复制代码
go get github.com/agiledragon/gomonkey/v2
2.使用示例

相关代码在gitee代码仓库的示例代码中,仓库地址请看博客开头

blog.go

go 复制代码
package user

import "tracer/model/user"

// UserInfoDao 查询所有用户信息
func UserInfoDao() (interface{}, error) {
	obj, err := user.GetAllUser()
	if err != nil {
		return nil, err
	}
	return obj, nil
}

blog_test.go

go 复制代码
package user

import (
	"errors"
	"fmt"
	"github.com/agiledragon/gomonkey/v2"
	"gorm.io/gorm"
	"testing"
	"tracer/dao"
	"tracer/model/user"
)

// TestUserInfoDao_AllScenarios 单个函数通过循环覆盖所有测试场景
func TestUserInfoDao_AllScenarios(t *testing.T) {
	// 1. 定义测试用例结构体:封装输入(打桩参数)和预期输出
	type testCase struct {
		name          string          // 用例名称,便于排查错误
		mockUsers     []user.UserInfo // 打桩 GetAllUser 返回的用户列表
		mockErr       error           // 打桩 GetAllUser 返回的错误
		expectedErr   error           // 预期 UserInfoDao 返回的错误
		expectedNil   bool            // 预期 UserInfoDao 返回的数据是否为 nil
		expectedCount int             // 预期返回的用户数量(正常场景有效)
	}

	// 2. 构造所有测试用例(正常场景 + 异常场景)
	testCases := []testCase{
		{
			name: "正常场景-返回2个用户",
			mockUsers: []user.UserInfo{
				{
					Model:    gorm.Model{ID: 1},
					UserName: "zhangsan",
					Password: "123456",
					Phone:    "13800138000",
					Email:    "zhangsan@test.com",
				},
				{
					Model:    gorm.Model{ID: 2},
					UserName: "lisi",
					Password: "654321",
					Phone:    "13900139000",
					Email:    "lisi@test.com",
				},
			},
			mockErr:       nil,
			expectedErr:   nil,
			expectedNil:   false,
			expectedCount: 2,
		},
		{
			name:          "正常场景-返回空用户列表",
			mockUsers:     []user.UserInfo{},
			mockErr:       nil,
			expectedErr:   nil,
			expectedNil:   false,
			expectedCount: 0,
		},
		{
			name:          "异常场景-GORM记录不存在错误",
			mockUsers:     nil,
			mockErr:       gorm.ErrRecordNotFound,
			expectedErr:   gorm.ErrRecordNotFound,
			expectedNil:   true,
			expectedCount: 0,
		},
		{
			name:          "异常场景-自定义查询错误",
			mockUsers:     nil,
			mockErr:       errors.New("数据库连接超时"),
			expectedErr:   errors.New("数据库连接超时"),
			expectedNil:   true,
			expectedCount: 0,
		},
	}

	// 3. 循环执行所有测试用例
	for _, tc := range testCases {
		dao.InitDb()
		// t.Run:为每个用例创建独立的测试上下文,互不干扰,便于定位用例错误
		t.Run(tc.name, func(t *testing.T) {
			// 步骤1:对 GetAllUser 进行动态打桩(每个用例独立打桩,避免相互影响)
			// 使用ApplyFunc打桩跨包函数
			patches := gomonkey.ApplyFunc(user.GetAllUser, func() ([]user.UserInfo, error) {
				// 返回当前用例预设的模拟数据和错误
				return tc.mockUsers, tc.mockErr
			})
			defer patches.Reset() // 每个用例执行完毕后重置打桩,避免污染其他用例

			// 步骤2:执行待测试函数 UserInfoDao
			_, err := UserInfoDao()
			if err != nil {
				fmt.Println(err)
			}
		})
	}
}

命令行执行命令

go test -cover

结果:

powershell 复制代码
PS D:\wyl\workspace\go\tracer\dao\user> go test -cover
PASS
coverage: 75.0% of statements
ok      tracer/dao/user 0.097s

如果报错,这个问题是数据库中不存在表:

复制代码
Error 1146 (42S02): Table 'tracer.user_info' doesn't exist
相关推荐
羊羊羊i1 小时前
使用client-go访问k8s集群
golang·kubernetes
源代码•宸2 小时前
goframe框架签到系统项目开发(实现总积分和积分明细接口、补签日期校验)
后端·golang·postman·web·dao·goframe·补签
YGGP2 小时前
【Golang】LeetCode 2. 两数相加
开发语言·leetcode·golang
Yy_Yyyyy_zz2 小时前
2025 技术年终总结|近七年 Golang 工程实践、AI 应用落地与技术创作回顾
开发语言·golang·ai编程
GrowingYi2 小时前
Go语言的特性
开发语言·后端·golang
YGGP3 小时前
【Golang】LeetCode 21. 合并两个有序链表
leetcode·链表·golang
看见繁华3 小时前
GO 教程
开发语言·后端·golang
Yy_Yyyyy_zz3 小时前
深入理解 Go 的多返回值:语法、编译原理与工程实践
开发语言·后端·golang
码农水水3 小时前
阿里Java面试被问:单元测试的最佳实践
java·面试·单元测试
天远云服4 小时前
Fintech硬核架构:解析天远贷前风险报告接口在Go微服务中的解析策略
微服务·架构·golang