知识卡片

写不出单元测试本身就是设计糟糕的信号,而不只是测试覆盖率不够的问题

普通读书笔记卡

内容

在模块级别重构阶段,单元测试之所以变成必需品,除了它能验证修改的正确性这个直接价值外,还有一层更重要的意义:写不出单元测试的代码,往往本身就意味着设计存在缺陷。想象一下要执行一个函数,却发现需要模拟十几个输入对象,而每个对象自己又依赖着更多其他对象需要一并模拟——这种”测试一个函数难到几乎不可能”的状况,直接暴露出这个模块承担了过多依赖、或者这个函数承担了过重的职责。如果一个模块无法被单独、独立地测试,从软件设计的角度审视,这本身就是不合格的——单元测试的可写性和代码设计质量之间存在一种因果关系:好的设计(职责单一、依赖清晰、耦合度低)天然容易被测试,而糟糕的设计(职责混乱、依赖繁多、耦合度高)天然难以被测试,测试的难易程度只是把这个设计问题的严重性直接暴露出来而已。这里还有一个容易被混淆的边界:只针对单一模块的测试才算真正意义上的单元测试,那些依赖多个模块共同完成的测试(比如在内存里模拟一个数据库、在上层代码里测试业务逻辑)并不算在内——这类跨模块的集成式测试虽然也有价值,但它不能反过来改善或暴露设计层面的问题,因为它绕过了”能不能独立测试单个模块”这个真正有诊断价值的检验点。这个案例揭示了一条评估设计质量的重要方法:与其直接争论”这个模块的设计好不好”这种主观判断,不如具体尝试为它写一个真正意义上的单元测试——如果这个尝试本身变得异常困难(需要模拟大量依赖),这个困难过程本身就是对设计质量最直接、最诚实的反馈。

参考来源

- 位置:《高可用架构(第1卷)》第5章《运维保障》"5.7 系统运维之如何应对烂代码"节,"5.7.1 改善可维护性"(源文件:_epub-src/OEBPS/Text/Chapter5_7_2.xhtml) - 结论依据:原文说明"更重要的意义在于,写不出单元测试的代码往往意味着糟糕的设计:模块依赖太多或者一个函数的职责太重,想象一下,想要执行一个函数却要模拟十几个输入对象……如果一个模块无法被单独测试,那么从设计的角度来考虑,无疑是不合格的",直接支撑本卡片结论。 - 原始内容:更重要的意义在于,写不出单元测试的代码往往意味着糟糕的设计:模块依赖太多或者一个函数的职责太重,想象一下,想要执行一个函数却要模拟十几个输入对象,每个对象还要模拟自己依赖的对象……如果一个模块无法被单独测试,那么从设计的角度来考虑,无疑是不合格的。