前言
日常开发中,很多新手会用 空 main.go 手动调用函数/Handler 调试代码,这种方式效率极低、无法回归、不能自动化、不支持团队协作。
Go 语言原生自带完整测试框架,无需任何第三方库,是官方推荐的唯一测试方案。本文整合所有核心规则、目录规范、代码示例、实操命令、避坑要点,可直接收藏/发布博客长期复用。
一、Go 测试核心强制规则(必记)
1. 文件命名规则
测试文件必须以 \_test\.go 结尾
-
业务文件:
xxx\.go -
对应测试文件:
xxx\_test\.go
2. 测试函数规则
-
函数名必须以
Test开头 -
参数固定:
\(t \*testing\.T\) -
无返回值、无参数自定义
标准格式:
Plain
func TestXxx(t *testing.T) {
// 测试逻辑
}
3. 目录存放规则(重点)
测试文件必须和被测试的业务文件【同目录、同包】
正确结构:
Plain
项目目录/
├── calc.go // 业务代码
├── calc_test.go // 对应测试代码(同目录)
├── handler.go // 业务代码
└── handler_test.go// 对应测试代码(同目录)
优势:无需导包、直接调用私有/公有方法
禁止:单独新建 test 文件夹存放测试文件(破坏包结构,不推荐)
4. 编译特性
所有 _test.go 文件不会被编译进生产二进制文件,零侵入业务代码。
二、测试命令运行目录 + 完整命令大全
1. 运行目录规则
-
测试当前目录 代码:在当前业务目录执行
go test -
测试子目录 代码:根目录执行
go test \./子目录名 -
测试整个项目所有包 :根目录执行
go test \./\.\.\.
2. 全套常用测试命令(日常必备)
Plain
# 执行当前目录测试,展示详细日志
go test -v
# 只执行指定的某个测试函数
go test -v -run TestAdd
# 查看代码测试覆盖率
go test -cover
# 生成覆盖率文件,打开网页可视化查看
go test -coverprofile=cover.out && go tool cover -html=cover.out
# 递归测试项目所有包
go test ./... -v
三、实战示例1:普通函数单元测试
适用于:工具函数、计算函数、业务逻辑函数等通用场景
1. 业务代码 calc.go
Plain
package demo
// Add 两数求和测试函数
func Add(a, b int) int {
return a + b
}
// Sub 两数求差测试函数
func Sub(a, b int) int {
return a - b
}
2. 测试代码 calc_test.go
采用表格驱动测试(Go 官方最佳实践,多用例统一管理)
Plain
package demo
import "testing"
func TestAdd(t *testing.T) {
// 定义多组测试用例:名称、入参、预期结果
testCases := []struct {
name string
a int
b int
want int
}{
{"正数相加", 1, 2, 3},
{"负数相加", -1, -2, -3},
{"零值相加", 0, 99, 99},
}
// 遍历执行用例
for _, tc := range testCases {
// 子测试:单个用例失败不影响其他用例
t.Run(tc.name, func(t *testing.T) {
res := Add(tc.a, tc.b)
// 断言校验结果
if res != tc.want {
t.Errorf("Add(%d,%d) = %d, 预期结果: %d", tc.a, tc.b, res, tc.want)
}
})
}
}
3. 执行测试
进入当前目录执行:go test \-v
四、实战示例2:原生 HTTP Handler 测试
核心优势:无需启动服务器、无需监听端口,原生模拟 HTTP 请求/响应,极速测试接口逻辑
1. 业务代码 handler.go
Plain
package demo
import (
"encoding/json"
"net/http"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
// GetUserHandler 原生HTTP接口
func GetUserHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
user := User{
ID: 1,
Name: "张三",
}
_ = json.NewEncoder(w).Encode(user)
}
2. 测试代码 handler_test.go
Plain
package demo
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)
func TestGetUserHandler(t *testing.T) {
// 1. 模拟HTTP请求
req := httptest.NewRequest("GET", "/user", nil)
// 2. 模拟响应记录器(捕获返回值)
w := httptest.NewRecorder()
// 3. 直接执行Handler逻辑
GetUserHandler(w, req)
// 4. 断言1:校验状态码
if w.Code != http.StatusOK {
t.Fatalf("状态码错误,实际:%d,预期:%d", w.Code, http.StatusOK)
}
// 5. 断言2:校验响应数据
var resp User
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
t.Fatalf("响应解析失败:%v", err)
}
if resp.ID != 1 || resp.Name != "张三" {
t.Errorf("返回数据异常,实际数据:%+v", resp)
}
}
五、实战示例3:Gin 框架 Handler 测试(高频场景)
适配绝大多数 Go 后端项目,写法通用、零改造
Plain
package demo
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
)
// 测试Gin接口
func TestGinUserHandler(t *testing.T) {
// 初始化Gin引擎
r := gin.Default()
r.GET("/user/:id", GetUserGinHandler)
// 模拟请求
req := httptest.NewRequest("GET", "/user/1", nil)
w := httptest.NewRecorder()
// 执行路由逻辑
r.ServeHTTP(w, req)
// 后续可自行添加状态码、数据断言,和上面示例一致
}
六、为什么放弃 main 手动测试?
-
可自动化:一行命令批量跑所有用例,无需手动运行
-
可回归:迭代代码后,一键校验旧功能是否报错
-
零成本:原生支持,无第三方依赖、不污染生产代码
-
可量化:支持测试覆盖率,清晰看到哪些代码未测试
-
可集成:完美适配 CI/CD 自动化部署流程
七、核心总结
-
规范:
xxx\.go对应xxx\_test\.go,同目录同包 -
格式:测试函数
TestXxx\(t \*testing\.T\) -
执行:当前目录
go test \-v,全局测试go test \./\.\.\. \-v -
普通函数:表格驱动测试 + 结果断言
-
HTTP接口:
httptest模拟请求,无需启动服务 -
测试文件不编译进生产包,安全无副作用
(注:文档部分内容可能由 AI 生成)