练习
学完本章你应该掌握
- 能创建
_test.go测试文件,写出TestXxx(t *testing.T)形式的基础测试函数,并正确使用go test、go test -v运行测试。 - 能根据场景选择
t.Fatalf和t.Errorf,知道什么时候应该立即终止当前测试,什么时候更适合把多个失败一次性报出来。 - 能使用表格驱动测试和
t.Run编写子测试,为同一个函数覆盖普通场景、边界值和错误路径,并能写出清晰的子测试名称。 - 能识别测试里常见的组织问题,例如测试文件命名错误、测试函数签名错误、子测试场景名缺失、失败信息不明确。
- 能使用
TestMain在整个包的测试开始前准备统一环境,并在全部测试结束后完成清理。 - 能把前面学过的字符串、切片、map、结构体、接口、错误处理和文件操作自然放进被测代码中,而不是只会为最简单的加法函数写测试。
简单
第 1 题:为 Add 写第一个测试
已知业务代码如下:
| |
请新建 add_test.go,完成下面要求:
- 编写测试函数
TestAdd(t *testing.T) - 调用
Add(1, 2) - 断言结果应该等于
3 - 如果断言失败,使用
t.Fatalf输出got和want
查看参考答案
文件名:add_test.go
| |
说明:这是最小可运行的 Go 单元测试写法,重点是 _test.go 文件名、TestXxx 函数名和 t *testing.T 参数都要写对。
第 2 题:修正不会被 go test 识别的测试
已知业务代码如下:
| |
下面这段测试代码保存在 sub.go 中,而且现在不会被 go test 正常识别。请改正它:
| |
要求:
- 改成正确的测试文件名
- 改成正确的测试函数名
- 改成正确的函数参数类型
查看参考答案
正确的文件名应为:sub_test.go
| |
这道题有 3 个关键修正:
- 测试文件要以
_test.go结尾,所以文件名不能叫sub.go - 测试函数必须以
Test开头,所以要把testSub改成TestSub - 参数类型必须是
*testing.T,不能写成testing.T
第 3 题:为切片求和函数写两个基础测试
已知业务代码如下:
| |
请新建 sum_test.go,完成下面要求:
- 编写
TestSum输入[]int{3, 5, 7}时,结果应该是15 - 编写
TestSumEmpty输入空切片时,结果应该是0 - 两个测试都使用
t.Fatalf
查看参考答案
文件名:sum_test.go
| |
说明:这道题虽然只用了切片和循环的基础知识,但真正要练的是“一个场景一个测试函数”的最基本测试组织方式。
第 4 题:使用 t.Errorf 连续检查偶数判断
已知业务代码如下:
| |
请编写 TestIsEven,在一个测试函数里连续检查下面 3 组数据:
2 -> true11 -> false0 -> true
要求:
- 如果某一组失败,不要立刻结束整个测试函数
- 使用
t.Errorf报告失败
查看参考答案
文件名:is_even_test.go
| |
说明:这里使用 t.Errorf 的好处是,就算第一组失败,后面的用例仍然会继续检查,方便一次看到所有不符合预期的场景。
一般
下面 5 题会开始大量使用表格驱动测试和子测试,让一组输入输出场景更清晰,也更接近真实项目中的测试写法。
第 5 题:用表格驱动子测试验证字符个数
已知业务代码如下:
| |
请编写 TestCountChars,要求使用“表格驱动测试 + t.Run 子测试”覆盖下面场景:
"Go"的字符数是2"Go语言"的字符数是4""的字符数是0
查看参考答案
文件名:count_chars_test.go
| |
说明:这道题顺手复习了前面字符串章节里的 []rune,而测试章节真正想练的是“把多组场景收进一张用例表,再用子测试逐个执行”。
第 6 题:用子测试覆盖成绩等级边界
已知业务代码如下:
| |
请编写 TestGrade,要求使用表格驱动子测试覆盖至少下面这些边界值:
-1 -> 非法0 -> D59 -> D60 -> C79 -> C80 -> B90 -> A101 -> 非法
查看参考答案
文件名:grade_test.go
| |
说明:等级函数最容易出错的地方通常不是“普通输入”,而是 59/60、79/80、90 这些边界,所以这里专门把边界值单独列成了测试用例。
第 7 题:修正一个写得不清晰的子测试
已知业务代码如下:
| |
下面这段测试也能表达“多场景校验”的意思,但写法不够符合本章建议。请把它改成更清晰的版本:
| |
要求:
- 子测试名称使用
item.name - 按本章推荐写法补上
item := item - 失败信息输出
got和want
查看参考答案
修正后的测试代码如下:
| |
说明:子测试名字一旦写清楚,go test -v 输出里就能直接定位是哪个场景失败;而失败信息里补上 got / want,排查起来会快很多。
第 8 题:为 map 查询函数补成功和失败子测试
已知业务代码如下:
| |
请编写 TestFindAge,要求使用子测试覆盖下面两个场景:
- 在 map 中能找到
"阿斌",结果是21, true - 在 map 中找不到
"小李",结果是0, false
提示:测试数据可以使用下面这张表:
| |
查看参考答案
文件名:find_age_test.go
| |
说明:这道题顺手复习了 map 的“存在 / 不存在”两条路径,而测试章节真正想强调的是:不要只测成功场景,失败路径同样应该被覆盖。
第 9 题:为结构体指针方法写测试
已知业务代码如下:
| |
请编写 TestStudentAddBonus,完成下面要求:
- 测试
Score=58时调用AddBonus(5),最后应该变成63 - 再补一个
AddBonus(0)的场景,确认分数不会被意外改错 - 建议使用子测试
查看参考答案
文件名:student_test.go
| |
说明:这道题表面上在测结构体和指针方法,实际是在练“每个子测试都自己准备数据,避免不同场景互相污染”。
进阶
下面 4 题会把错误路径、接口、文件操作和完整测试组织方式一起串起来,更接近真实项目里的 Go 测试代码。
第 10 题:为返回 error 的年龄解析函数补成功和失败场景
已知业务代码如下:
| |
请编写 TestParseAge,要求使用子测试覆盖下面两个场景:
"18"应该返回18和nil"abc"应该返回非空错误,并且错误信息中至少包含parse age
提示:可以使用 strings.Contains
查看参考答案
文件名:parse_age_test.go
| |
说明:这道题重点不是 strconv.Atoi 本身,而是训练你在测试里同时覆盖“成功路径”和“错误路径”,这正是本章最容易漏掉的一类场景。
第 11 题:为接口统一计算函数写测试
已知业务代码如下:
| |
请编写 TestCalcTotal,要求至少覆盖下面两个场景:
- 正常场景:
CourseOrder{Title: "Go 接口课", Price: 128}BookOrder{Title: "Go 练习册", Price: 99, Discount: 20}结果应该是207 - 空切片场景:
结果应该是
0
建议使用子测试。
查看参考答案
文件名:calc_total_test.go
| |
说明:这道题顺手复习了接口、多态和切片,但测试章节真正想练的是“同一个函数面对不同业务场景时,如何用子测试把用例组织清楚”。
第 12 题:使用 TestMain 为文件读取测试准备环境
已知业务代码如下:
| |
请编写 reader_test.go,完成下面要求:
- 写一个包级
TestMain(m *testing.M) - 在所有测试执行前创建测试文件
lesson.txt文件内容为:
| |
- 在所有测试执行后删除这个文件
- 编写
TestReadFirstLine读取lesson.txt时,结果应该是Go 单元测试 - 编写
TestReadFirstLineMissing读取不存在的文件时,应该返回非空错误
查看参考答案
文件名:reader_test.go
| |
说明:TestMain 适合放“整个包共享的准备和清理逻辑”。这道题把前一单元的文件操作自然接进来了,但真正要练的是 m.Run()、统一准备和统一清理这套测试组织方式。
第 13 题:综合为班级成绩报告写一组完整测试
已知业务代码如下:
| |
请编写 TestBuildReport,要求使用表格驱动子测试覆盖至少下面两个场景:
- 正常场景:
| |
期望结果:
passCount = 3
avg = 77.25
top = Student{Name: "小张", Score: 92}
err = nil
- 空切片场景: 期望返回非空错误
提示:比较浮点数平均分时,可以使用 math.Abs
查看参考答案
文件名:report_test.go
| |
说明:这道题把结构体、切片、循环、条件判断、错误返回和浮点数比较全都放进了被测代码里,而测试章节真正想练的是“如何把正常路径和错误路径一起组织进一套清晰的测试用例”。