本层判断标准

把一条改坏的数据放进输入,这个测试会不会变红?

不会,说明用例只走了正常路径,或断言的是替身自己设的常量。补一条会失败的输入,再谈覆盖率的数字——那个数字只有在「改坏会红」的前提下才有意义。

单元测试挡什么、挡不住什么

它的判断到函数为止:给一组输入,看输出对不对。装不进「输入—输出」这个形状的事,它一概不管。

要判断的事单元测试交给谁
过滤逻辑有没有把别的范围算进来挡得住,改坏输入会红—
越权与不存在返回的是不是同一个错误挡得住,断言错误值—
会话里的范围值有没有传进处理函数挡不住,函数在测试里是被直接调用的接口测试
路由、序列化、鉴权中间件不该由它测接口测试
回答里有没有引用材料之外的内容写不出可判定的字段评测
页面文案与空状态不在函数这一层界面测试

边界要说清:它既便宜又快,所以能覆盖每一个分支;代价是它只保证自己断言过的那些行为,没写进用例的分支,覆盖率再高也是 0。

长什么样子

一条测试就是「准备—执行—断言」三段,缺一段就不算一条。名字里写清输入条件与期望。

一条测试的三段

TestListByIds_他范围的那条不出现(t *testing.T)
├── 准备  造两组输入:本范围一条 + 他范围一条
├── 执行  调被测函数一次,拿到返回值
└── 断言  结果里不含他范围那条;右值来自被测逻辑

要认的是这个形状:断言必须比较两个值,且右值是被测逻辑的输出。断言替身设的常量、断言调用次数、只走正常路径,形状上都是「三段里少了一段」。

助手交出来的样子

TestBuildRows(t *testing.T)
├── 准备  造一组输入
├── 执行  rows, err := buildRows(input)
└── 断言  assert.NoError(err)
          assert.NotNil(rows)
          assert.Len(rows, len(input))    右值来自输入,不是被测逻辑

认它的办法:改坏被测逻辑,它还绿。三条断言一条都没说「结果里该有什么」——把实现改掉,长度照样等于输入长度。这类测试语法完全正确,还能把覆盖率抬上去,评审里最难看出来。

典型应用示例:改坏一条数据会不会红

场景:让助手给「按范围过滤列表」这个纯函数补测试。输入是已经取出的记录与会话里的范围值,不碰数据库。

有它 用例表里有正常、边界、失败三类输入,每组输入的标识写进子测试名。把一条记录的范围值改坏再跑,那一条立刻变红,失败报告直接点出是哪一组输入——不用打开测试源码数第几行。
没有它 只留一条正常路径的用例,改坏数据照样全绿;失败报告只有一行「断言失败」,定位不到是哪个范围,排查要重新跑一遍。

基本注意点

写完测试按这三问自查,任何一问答不出,这条测试还不算完成。

问什么答「否」说明什么
把数据改坏,它会不会红?只走了正常路径,或断言的是替身设的常量
断言的右值是不是被测逻辑的输出?测的是替身自己,被测代码根本没执行
失败报告能不能直接定位到是哪一组输入?用例名没有带输入标识,定位要靠重跑

反过来也要认账:写不出测试通常不是没时间,是代码形状的问题——只能写「调用不报错」,说明结果藏在全局状态里;要连库才能构造输入,说明计算与数据访问没分开。出现这两种,先改代码形状,不要加 skip。纯展示的模板与生成代码不该有单元测试,让它们进检查的排除名单。

测试文件与被测文件同名同目录,命令挂进门禁那一条入口命令。覆盖率只回答「新增行有没有被执行过」,不回答断言对不对——所以跳过的用例必须是 0 且不增长,临时跳过要在用例名里写恢复时间;需要真实数据库的用例连不上就跳过,但跳过与通过必须是报告里不同的两行,不能换成内存库把方言差异盖掉。