随笔十二:单元测试
粒度是指测试所消耗的资源和允许它做的事情。
范围是指测试打算验证多少代码。
我们的”单元测试“一词用来指范围相对狭窄的测试,例如单个类或者方法的测试。
测试最重要的是提高工程师的工作效率。
- 小型测试时快速和确定的,允许开发人员在工作流程中频繁的运行它们并获得即使反馈。
- 在编写生产代码的同时编写对应的单元测试大司马,更容易,也令工程师将测试的重点放在正在处理的代码上,而无需设置和理解更大的系统。
- 编写单元测试又快有容易,因此能促进达成较高的测试覆盖率水平。
- 单元测试失败了,更容易被理解哪里出了错。
- 它们可以作为文档和示例
可维护性的重要性
糟糕的测试必须在集成前就被修复,防止影响到未来的工程师。
防止脆弱的测试
脆弱的测试时指在对生产代码进行不相关的变更,而又不会引起任何真正的缺陷时,但却令其失败的那些测试。
努力做到不更改测试
最理想的测试是还是不变的:在编写之后,除非被测系统的需求发生变化,否则它永远不需要改变。
以下几种变更说明:
-
- 纯粹的重构:系统的测试用例不应该改变
- 新特性:系统的现有行为不应该受影响
- 缺陷修复:缺陷的存在表明初始化测试套件中缺少一个用例。
- 行为改变
通过公共API进测试
确保测试不需要改变,最重要的方法是当针对调用被测系统编写测试时,调用方式与被测试系统而用户调用方式相同,也就是说,调用他的公共API,而不是调用他的具体实现细节。
·为一个单元定义一个合适的范围,有一些一些经验:
-
- 如果一个方法或类的存在知识为了支持一个或者两个其他类,那么他可能不应该被视为一个独立的单元,他的功能应该有其所支持的那些类来测试,而不是直接测试它
- 如果一个包或类被设计成任何人都可以访问,而不咨询其所有者,那么计划肯定的是,它应该构成了一个应该直接测试的测试的单元。
- 如果一个包或类只能被拥有它的人访问,大hi它被设计为在一定范围内提供有用的通用功能,它也因该被视为一个单元并直接进行测试。
测试状态,而不是测试交互
测试通常依赖于实现细节的另一种方式,不是测试调用系统的那些方法,而是如何验证这些调用的结果。
加护测试往往比状态测试更脆弱,同样的原因是测试私有方法比测试共有方法更脆弱;
交互测试检查系统都是通过什么样的调用过程得到的结果的,二通常你应该只关心结果是什么。
编写清晰的测试
测试失败有两个原因:
-
- 被测试系统有问题或不完整,这个结果正式设计测试的目的,提醒你有缺陷,以便可以修复他们。
- 测试本身存在缺陷。
使测试完整简洁
帮助测试实现清晰性的两个高层属性是完整性和简洁性。
一个测试的主体应该包含理解他所需的所有信息,而不包括任何无关的或分散注意力的信息。
测试行为,而不是方法
许多工程师的第一直觉是尝试将测试结构与代码结构相匹配,以便每个生产方法都有对应的测试方法,这种模式一开始可能很方便,但是随着时间的推移,它会导致一些问题:随着被测试的方法变得越来越复杂,其测试也变得越来越复杂,变得越来越难以理解。
有一个好的方法:预期为每个方法编写测试,不如为每个行为编写测试。
行为是系统在特定状态下如何响应一系列输入的保障。
行为通常可以用“given”、“when”、“then”来表示。
分割单个测试所需的额外样板文件是非常值得的,并且结果测试比原始测试要清楚的多。
行为测试往往比方法测试更清晰,原因有三:
-
- given:定义系统的预设条件
- when:定义要在系统上执行的操作
- then:验证结果
当测试确实要验证多个步骤过程中的每个步骤时,可以定义when/then的交替序列。长代码也可以用and将他们分割来变得更具描述性。
最常见的反模式时将断言分散到对被测试系统的多个调用中。
以方法命名
面向方法的测试通常以方法命名。
测试命名应该概括他正在测试的行为。一个好的名称描述了系统正在进行的操作和预期的结果。
只要要在单测中保持不同命名策略的一致就可以。
不要把逻辑放在测试中
在测试代码中,坚持使用更直接的代码,而不是聪明的代码,并且要考虑在使测试更具描述性和意义时容忍一些重复。
编写清晰的失败信息
要在断言中明确的输出失败信息
测试与代码共享:DAMP ,而不是DRY
如果测试变得太复杂,以至于我们感觉自己需要对测试进行测试,以确保他们正常工作,那么就会出问题。
测试代码不应该完全是DRY(不要重复自己),而是应该努力保持DAMP(描述性和有意义的短语),只要这种重复使测试更简单、更清晰,那么测试中有一点重复使可以的。
DMAP不能替代DRY,他是对DRY的补充。
Helper方法和愁死基础设施仍然可以通过使测试更加简洁、分解出于所测试的特性行为无关的重复步骤来帮助使测试更加清晰。
共享值
在每个测试类开始设置共享值,随着测试套件的增加,他会导致一些问题,首先是很难理解为什么要选择这样一个特定性的值进行测试。
工程师沉迷于使用共享值,因为为每个测试构造单独的值可能很繁琐。
为了实现这一目的,更好的方法是使用helper方法来构造数据。测试作者只要使用这些hepler方法指定他们所关心的值,并为所有其他值设置合理的默认值就可以了。
共享设置
测试之间共享代码的另一个方法是通过共享一个设置方法或初始化方法。
许多测试框架允许工程师在运行套件中的每个测试之间,定义一个要首先执行的初始化方法,从而使测试更加清晰和简洁。
共享helper和验证
跨测试共享代码的最后一种常见方式是通过测试方法主体调用的helper方法。
helper的一种常见类型是对被测试系统执行一组公共断言的方法。
本章要点
- 努力做到不改变测试
- 通过公共API进行测试
- 测试状态,而不是交互
- 使你的测试完整而简洁
- 测试行为,而不是方法
- 强调行为的结构化测试
- 以行为给测试命名
- 不要把逻辑放进测试
- 编写清晰的失败信息
- 在共享测试代码时,请遵循“DAMP高于DRY”原则
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!