📚 原创系列: “Go语言学习系列”
🔄 转载说明: 本文最初发布于"Gopher部落"微信公众号,经原作者授权转载。
🔗 关注原创: 欢迎扫描文末二维码,关注"Gopher部落"微信公众号获取第一手Go技术文章。
📑 Go语言学习系列导航
🚀 第二阶段:基础巩固篇本文是【Go语言学习系列】的第20篇,当前位于第二阶段(基础巩固篇)
- 13-包管理深入理解
- 14-标准库探索(一):io与文件操作
- 15-标准库探索(二):字符串处理
- 16-标准库探索(三):时间与日期
- 17-标准库探索(四):JSON处理
- 18-标准库探索(五):HTTP客户端
- 19-标准库探索(六):HTTP服务器
- 20-单元测试基础 👈 当前位置
- 21-基准测试与性能剖析入门
- 22-反射机制基础
- 23-Go中的面向对象编程
- 24-函数式编程在Go中的应用
- 25-context包详解
- 26-依赖注入与控制反转
- 27-第二阶段项目实战:RESTful API服务
📖 文章导读
在本文中,您将了解:
- Go语言测试的基本概念和工作原理
- 编写有效单元测试的最佳实践
- 表格驱动测试与测试辅助函数
- 模拟外部依赖与测试替身
- 测试覆盖率分析与持续集成
单元测试是确保代码质量和可靠性的重要工具,而Go语言内置的testing包提供了简单而强大的测试支持。本文将从基础开始,深入讲解Go语言测试的各个方面,帮助您构建更健壮、更可维护的Go程序。
1. Go测试的基本概念
1.1 什么是单元测试
单元测试是针对程序中最小可测试单元(通常是函数或方法)的测试。在Go中,这通常指对特定函数或方法的输入和输出进行验证,确保它们按预期工作。单元测试的目标是:
- 验证代码的正确性
- 防止回归(确保修改不会破坏现有功能)
- 促进代码重构
- 提供文档和示例
1.2 Go测试文件命名约定
Go的测试文件遵循以下命名约定:
- 测试文件以
_test.go结尾 - 测试文件通常与被测代码在同一包中
- 测试函数名以
Test开头
例如,如果你有一个名为calculator.go的文件,对应的测试文件应命名为calculator_test.go。
1.3 测试函数结构
Go测试函数的基本结构如下:
func TestXxx(t *testing.T) {
// 测试代码
}
要点:
- 测试函数必须以
Test开头,后跟大写字母开头的单词 - 测试函数必须接受
*testing.T类型的参数 - 测试函数没有返回值
2. 编写基本测试
让我们通过具体例子学习如何编写基本测试。首先,创建一个简单的计算器包:
// calculator.go
package calculator
// Add 返回两个整数的和
func Add(a, b int) int {
return a + b
}
// Subtract 返回两个整数的差
func Subtract(a, b int) int {
return a - b
}
// Multiply 返回两个整数的乘积
func Multiply(a, b int) int {
return a * b
}
// Divide 返回两个整数的商,如果除数为0则panic
func Divide(a, b int) int {
if b == 0 {
panic("除数不能为0")
}
return a / b
}
现在,为这些函数编写测试:
// calculator_test.go
package calculator
import (
"testing"
)
func TestAdd(t *testing.T) {
result := Add(3, 2)
expected := 5
if result != expected {
t.Errorf("Add(3, 2) = %d; 期望 %d", result, expected)
}
}
func TestSubtract(t *testing.T) {
result := Subtract(5, 2)
expected := 3
if result != expected {
t.Errorf("Subtract(5, 2) = %d; 期望 %d", result, expected)
}
}
func TestMultiply(t *testing.T) {
result := Multiply(4, 3)
expected := 12
if result != expected {
t.Errorf("Multiply(4, 3) = %d; 期望 %d", result, expected)
}
}
func TestDivide(t *testing.T) {
result := Divide(6, 2)
expected := 3
if result != expected {
t.Errorf("Divide(6, 2) = %d; 期望 %d", result, expected)
}
}
func TestDivideByZero(t *testing.T) {
// 使用匿名函数封装可能panic的操作
defer func() {
if r := recover(); r == nil {
t.Errorf("除以零应该引发panic")
}
}()
Divide(6, 0)
}
2.1 运行测试
可以使用go test命令运行测试:
go test # 运行当前包中的所有测试
go test -v # 详细模式,显示每个测试函数的结果
go test ./... # 运行当前目录及其子目录中的所有测试
go test -run TestAdd # 只运行名称匹配"TestAdd"的测试
2.2 理解测试输出
详细模式下的输出示例:
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
=== RUN TestSubtract
--- PASS: TestSubtract (0.00s)
=== RUN TestMultiply
--- PASS: TestMultiply (0.00s)
=== RUN TestDivide
--- PASS: TestDivide (0.00s)
=== RUN TestDivideByZero
--- PASS: TestDivideByZero (0.00s)
PASS
ok github.com/yourusername/calculator 0.002s
如果测试失败,输出将包含错误信息:
=== RUN TestAdd
--- FAIL: TestAdd (0.00s)
calculator_test.go:14: Add(3, 2) = 6; 期望 5
FAIL
exit status 1
FAIL github.com/yourusername/calculator 0.002s
3. 表格驱动测试
Go社区推崇"表格驱动测试"的模式,这种模式通过创建测试用例表格,使测试更加简洁和可维护。
func TestAdd_TableDriven(t *testing.T) {
// 定义测试用例表
tests := []struct {
name string // 测试名称
a, b int // 输入参数
expected int // 期望结果
}{
{
"正数相加", 3, 2, 5},
{
"负数相加", -3, -2, -5},
{
"正负相加", 3, -2, 1},
{
"零值处理", 0, 0, 0},
{
"大数相加", 1000000, 1000000, 2000000},
}
// 遍历测试用例表
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Add(tt.a, tt.b)
if result != tt.expected {
t.Errorf("Add(%d, %d) = %d; 期望 %d",
tt.a, tt.b, result, tt.expected)
}
})
}
}
表格驱动测试的主要优势:
- 易于添加新的测试用例
- 代码重复减少
- 测试报告更有信息量
- 更好的测试覆盖范围

最低0.47元/天 解锁文章
844

被折叠的 条评论
为什么被折叠?



