|
|
3 дней назад | |
|---|---|---|
| .. | ||
| README.md | 3 дней назад | |
| main.go | 3 дней назад | |
| main_test.go | 3 дней назад | |
共享根目录的 go.mod,仅使用标准库,无需安装测试框架。本课暂不使用并行测试、mock、基准测试或模糊测试。
package main
import "testing"
func TestAdd(t *testing.T) {
got := add(2, 3)
want := 5
if got != want {
t.Fatalf("实际=%d,期望=%d", got, want)
}
}
_test.go 结尾。func TestXxx(t *testing.T),Test 后的名字不能以小写字母开头。本课统一使用 TestAdd 这样的名称。testing 是标准库包,T 是它提供的类型,*testing.T 是指针类型,t 是参数名。package main,可以直接访问本课未导出的 add 和 parseScore。一次测试的核心是:准备输入 → 调用函数 → 比较实际与期望 → 不一致就报告失败。got、want、tt 都只是惯用变量名,不是关键字。
| 方法 | 标记失败 | 是否继续当前测试 |
|---|---|---|
t.Logf(...) |
否,只记录信息 | 继续 |
t.Errorf(...) |
是 | 继续 |
t.Fatalf(...) |
是 | 停止当前测试 |
它们支持格式字符串。测试正常执行完且未标记失败,就是通过,不需要额外调用成功方法。
Fatalf 在子测试中只停止当前子测试的执行,其他用例通常仍会运行;不要把它理解为立即结束整个测试进程。普通 return 也能结束当前测试函数,但不会自动把它标记为失败。
不要在测试中仅用 fmt.Println 输出“失败”来代替 t.Errorf / t.Fatalf,否则 go test 可能仍报告通过。
tests := []struct {
name string
a int
b int
want int
}{
{name: "positive", a: 2, b: 3, want: 5},
{name: "zero", a: 0, b: 0, want: 0},
}
这是第八课学过的“结构体切片”,每条记录表示一组输入与期望结果,不是特殊测试语法。
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := add(tt.a, tt.b)
if got != tt.want {
t.Errorf("实际=%d,期望=%d", got, tt.want)
}
})
}
按层理解:
()。}) 分别关闭匿名函数体和 Run 的调用参数列表。本课没有调用 t.Parallel,子测试回调结束后才继续下一次循环,先按顺序理解。并发执行以及旧版本 Go 循环变量捕获的差异留到后续,不要随意添加并行语句。
TestParseScore 的 wantErr 表示“期望本次调用发生错误”,不是错误文本本身。
got, err := parseScore(tt.input)
hasErr := err != nil
if hasErr != tt.wantErr {
t.Fatalf("错误=%v,期望发生错误=%t", err, tt.wantErr)
}
if tt.wantErr {
return
}
先比较是否发生错误与预期是否一致:
本例覆盖 0、100 两个合法边界,以及 -1、101、非整数文本和空文本。错误只检查是否存在,不逐字匹配标准库错误信息。它不验证错误的具体分类;更精细的错误断言留到错误进阶。
以下命令在仓库根目录执行:
# 运行本课所有测试,显示每个用例和日志
go test -v ./lessons/14_testing
# 强制重新执行,不复用成功的测试缓存
go test -count=1 -v ./lessons/14_testing
# 只运行最简单的 TestAdd,不匹配 TestAddTable
go test -v ./lessons/14_testing -run '^TestAdd$'
# 只运行一个指定子测试
go test -v ./lessons/14_testing -run '^TestParseScore$/^upper_bound$'
# 查看本课语句覆盖率
go test -cover ./lessons/14_testing
# 运行全仓库测试
go test ./...
-run 接收正则表达式,不是普通字符串包含查找;^、$ 分别约束开头与结尾。子测试用 / 分层筛选。
显示 (cached) 表示复用了之前的成功测试结果,不是本次重新执行。覆盖率高不代表逻辑一定正确;本课只测试业务函数,没有覆盖 main 的终端展示,因此总覆盖率不会是 100%。
普通程序仍可以编译运行,在仓库根目录执行:
New-Item -ItemType Directory -Force bin/14_testing | Out-Null
go build -o ./bin/14_testing/testing_demo.exe ./lessons/14_testing
.\bin\14_testing\testing_demo.exe
普通程序预期输出:
2 + 3 = 5
转换后的成绩:85
运行自动测试:go test -v ./lessons/14_testing(在仓库根目录执行)
也可以用 go run ./lessons/14_testing,但它只运行 main,不运行 _test.go 中的测试。
go test 本身也需要先编译。若想单独保留测试可执行文件:
go test -c -o ./bin/14_testing/lesson14.test.exe ./lessons/14_testing
.\bin\14_testing\lesson14.test.exe '-test.v'
直接运行测试产物时使用 -test.v 等测试二进制参数,而不是 go test 的 -v。在 PowerShell 中给带点的参数加引号,确保原样传入。修改源码或测试后需要重新编译。当前目录的 bin 已在前面的命令中创建。
" 85 ",期望错误,因为 Atoi 不会自动去除空白。go test -count=1 ./...,确认通过。本课测试代码应直接放在本课目录。若另建 practice 子目录,需要把被测函数和对应测试一起放进去,再执行 go test -v ./lessons/14_testing/practice,不能只复制测试却指望自动访问父目录函数。
下一课学习包与模块,把可复用函数拆到自定义包中并正确导入。