2.5 单元测试的误区与反模式


本节摘要:有些写法让测试"亮假绿灯"——看起来全通过,实际没验证任何该验证的东西。本节盘点六种最高发的反模式,各配一个坏例子和一个补救,目标是让你一读到自己的测试就心里有数。

假绿灯比红灯更贵

红灯至少还在说"有事",假绿灯则是明明有风险,页面却显示全绿,等于把警报线剪了。反模式就是这个剪线专业户。它能存在这么久,不是因为大家坏,而是因为"跑起来绿"这个反馈太有迷惑性——测试通过得太容易,人就懒得深究它到底测了什么。下面六种反模式,是测试诊所里复查率最高的回头客。

反模式一:测实现,不测行为

坏例子:断言内部状态被置成某值,或断言某个私有 helper 被调用。这一改实现,测试就碎。

def test_internal_flag_flips(self): # 反模式:盯内部 self.obj.compute() assert self.obj._done is True # 私有内部,改签名即碎

补法:断言结果或对外接口,包住行为而非暴露内部——换成 assert compute() == EXPECTED

反模式二:断言恒真

坏例子:只断言函数跑完没抛异常,或断言一个必然真的条件。形式上有断言,实质上零保险。

def test_returns_something(self): # 反模式:assert something value = func() assert value is not None # 几乎恒真,等于没测

补法:给定明确输入 + 明确预期输出,断言具体值而不是"非空"。

反模式三:测试间共享可变的全局态

坏例子:两个测试共用同一个模块级列表,一个改一个读,顺序不同结果不同,间歇性闪红。

_doc_months_cache = [] def test_a(): _doc_months_cache.append(1) # 污染 def test_b(): assert len(_doc_months_cache) == 0 # 看运气

补法:每个测试用独立夹具初始化状态,别让用例共享可写的全局容器;确需共享也要用无副作用的 fixture 每次重建。

反模式四:一次测十件事

坏例子:一条用例塞进一堆断言,失败时面对一堵墙看不出哪处错。

def test_whole_order_flow(self): # 反模式:一锅烩 assert order.items assert order.total assert order.stock assert order.tax # 哪里黄了不知道

补法:拆成面向单个行为的原子用例,各自断言一点,配合 2.1 的"原子铁律"。

反模式五:为覆盖率而堆样例

坏例子:为了把某行变成 100% 覆盖,重复堆同质断言,命中率上去了,可这些样例进化成垃圾——改需求时它们全是噪音。

def test_x_1(): assert fn(1) def test_x_2(): assert fn(2) # 与 test_x_1 几乎重复 def test_x_3(): assert fn(3) # 只为了把参数跑满

补法:用参数化把等价样例合并成一条"画一条边界"的用例,真正把时间花在变种边界和异常路径上。参数化可以用 pytest 的 @pytest.mark.parametrize,代价小信息密度高。

反模式六:依赖外部网络的单元测试

坏例子:单元测试里直接访问公网第三方 API,等于把测试的稳定性和外部服务绑定。偶发超时就闪红,人人学会无视红灯。

补法:坚持 2.2 的依赖注入,把外部调用改成可注入的端口,测试里用替身,真网络验证留给集成层。

一张反模式速查表

反模式 一句话识别 补救一根筋
测实现 断言私有/内部调用 断言行为与结果
断言恒真 断言"非空""不抛" 断言具体值
共享全局态 测试间共写一个容器 用独立夹具重建
一锅烩 一条带十个断言 拆原子用例
堆样例 同质参数刷覆盖 参数化 + 边界
依赖网络 单元里真联网 注入端口

这一关最重要的一课

识别反模式的关键不是背这张表,而是建立一种敏感:绿灯亮的时候问一句"它到底拦住了什么真实风险"。答得上来,说明这盏灯有骨头;答不上来,它多半只是占了绿灯的名额。测试的价值不取决于条数,而取决于每一条在发生变化时真能挺身而出拦住一个回归。

反模式会"传染",看完要晒太阳

反模式有一个反直觉的特点:它们不是孤立存在,而是会传染。团队每新写一条"断言恒真""一锅烩",都会给下一条当榜样——新人照旧测试抄,越抄越像,几轮迭代下来整个套件统一成一个"跑着全绿但我们都不敢动"的大染缸。所以治理反模式别只想"这条怎么修",更要问"这条是从哪条抄来的、源头在谁的评审里"。

对付传染,最省力的两个动作是 Code Review 里刻一条硬规矩和定期的反模式巡诊。前者容易理解:新测试一律过审,审不过的不合。后者则值得给个节奏:每两个月挑一个下午,把新增测试对着 2.5 这张表扫一遍,像扫地一样清一遍——清出来的不是羞辱某个同事,而是证明这套件还能继续被信任。反模式的尽头从来不是"零反例",而是"一出现就有人立刻指出来并修掉"。

和学生气的"我加了测试就安心"道别

还有一种心态层面的误区比技术反模式更难治:把"加了测试"本身当成安全感的来源,却从不检查它到底护不护得住。这类测试写起来很快,过一眼也全绿,但往往是"断言恒真"和"堆样例"的温床——因为它满足的是作者的安心感,而不是系统的回归防护。判断自己有没有掉进去,有一个很直接的问法:上个月有没有哪条测试,在你改坏代码后真的变红救了你一命?如果一条都没有,而那批测试每天都在绿,你很可能正在为安心感写测试,而不是为回归写测试。

本节要点回顾

  • 假绿灯更贵:亮绿≠有效,可能拦了根被剪的线。
  • 六种反模式:测实现、恒真、共享全局态、一锅烩、堆样例、依赖网络。
  • 识别靠一问:亮绿时问问它拦住的是不是真实风险。
  • 补救一根筋:测行为、断具体、独立夹具、拆原子、参数化、注入端口。
  • 价值不靠条数:靠"变化发生时它真能挺身而出"。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U