Files
zhuangang/topics/backend/go/03-testing-engineering.md
T

3.5 KiB
Raw Blame History

id, title, type, domain, stack, tags, level, prerequisites, est_hours, related, week, order
id title type domain stack tags level prerequisites est_hours related week order
backend.go.testing-engineering 测试和工程化:表格驱动测试、httptest、模块、项目结构 lesson backend
go
Go
测试
httptest
go mod
项目结构
intermediate
backend.go.basics-types
3
backend.go.interview
backend.gin.routing-binding
1
2
3
3

测试和工程化:表格驱动测试、httptest、模块、项目结构

解释说明

  • go test:xxx_test.go 里的 TestXxx(t *testing.T) 会被自动执行。t.Run 定义子测试;-race 检测数据竞争;-cover 看覆盖率;BenchmarkXxx 做性能测试。
  • 表格驱动测试:把多组“输入 + 期望”写成切片循环执行,是 Go 社区的惯例,新增用例只加一行。
  • httptest:httptest.NewRecorder() 不启动端口直接测 handler;httptest.NewServer 启动一个真实的本地服务,用来模拟第三方接口。
  • 模块:go mod init 模块名 生成 go.mod;go get 加依赖;go mod tidy 清理。go.sum 记录依赖的校验和,要提交。
  • 项目结构(社区常见做法,不是强制标准):cmd/<程序名>/main.go 放入口;internal/ 放不希望被其他模块导入的代码(编译器强制);按职责分包(handler、service、store),避免一个 utils 包装所有东西。

案例

接口层的测试一般这样分:handler 测试用 httptest.NewRecorder + 内存版 store,覆盖参数校验、状态码、鉴权;store 的集成测试连真实数据库(没有数据库时跳过);调用第三方的代码用 httptest.NewServer 模拟各种返回(超时、500、格式错误)。

代码示例

// 文件:slug.go
package slug

import (
	"strings"
	"unicode"
)

// Make 把标题转成 URL 片段:小写、空白和标点变成 -,去掉首尾 -
func Make(title string) string {
	var b strings.Builder
	dash := false
	for _, r := range strings.ToLower(title) {
		if unicode.IsLetter(r) || unicode.IsDigit(r) {
			b.WriteRune(r)
			dash = false
		} else if !dash && b.Len() > 0 {
			b.WriteByte('-')
			dash = true
		}
	}
	return strings.TrimSuffix(b.String(), "-")
}
// 文件:slug_test.go
package slug

import "testing"

func TestMake(t *testing.T) {
	cases := []struct {
		name, in, want string
	}{
		{"普通", "Hello World", "hello-world"},
		{"多个符号", "Go -- 并发!!", "go-并发"},
		{"首尾空白", "  Redis  ", "redis"},
		{"空", "", ""},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			if got := Make(c.in); got != c.want {
				t.Fatalf("Make(%q) = %q, want %q", c.in, got, c.want)
			}
		})
	}
}

func BenchmarkMake(b *testing.B) {
	for b.Loop() { // Go 1.24+ 的写法;旧版本用 for i := 0; i < b.N; i++
		Make("Hello World From Go")
	}
}

运行:go mod init slug && go test -v -race -cover ./... && go test -bench=. ./...

易错点

  • 测试之间共享全局状态(全局 map、同一张表)会让测试结果依赖执行顺序;每个测试自己准备数据,t.Cleanup 清理。
  • 只测“正常路径”:错误路径(参数错、下游失败、超时)才是线上出问题的地方。
  • internal 包只能被它的父目录及子目录导入;别的模块导入会编译失败——这是故意的。
  • 不提交 go.sum 会让别人构建时无法校验依赖。

面试常问

见 Go 面试题:怎么测试依赖数据库或第三方接口的代码、internal 的作用。