单元测试

目标

 

 

根本目标:

  软件的可持续发展

原因:代码会随着时间腐化。每次改动都会增加软件熵,增加无序性。代码是负债,而不是资产。

解决方案:

  • 持续清理和重构
  • 编写单元测试进行校正

收益:

  • 减少调试时间
  • 改善代码质量
  • 帮助理解功能
  • 增加重构自信

代价:单元测试也是代码,需要消耗时间和精力,因此需要平衡成本和收益。

如何收益最大化:

  • 代码的价值不同。应区分核心代码和边缘代码
  • 对核心代码编写单元测试
  • 编写有用的单元测试

开发人员应当:

  • 学会辨别单元测试的好坏
  • 通过重构使代码恢复价值

宁愿不写单元测试也不要写没有价值的单元测试。

评估方法

单元测试的评估指标是代码质量的晴雨表。

代码覆盖率

分支覆盖率l

高覆盖率不代表代码质量高,但低覆盖率一定意味着代码质量低。单元测试不是为了提高评估指标,而是为了提高代码质量。

定义

单元测试是开发者编写的一小段代码,用于检验被测代码单元的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或场景)下某个特定函数(或功能)的行为。单元测试是对软件基本组成单元进行的测试,所谓单元指的是:最小的被测功能模块,它可以是一个类,也可以是一个方法。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的最小单元在与其他部分相隔离的情况下进行测试。

单元测试的三个基本特点:

伦敦派

隔离性是指被测组件或系统与其依赖之间的隔离。

 

好处:

  • 方便快速定位测试失败原因
  • 方便构造依赖对象
  • 方便组织测试代码

经典派

隔离性是指功能的隔离,亦即应当隔离单元测试代码本身,而不是被测试的代码单元。测试的目的是验证功能是否正确,而不是验证代码单元是否正确。因此,代码结构应当按照功能单元来组织,测试也应该如此。如此一来,不同的单元测试可以以任意顺序执行(串行、并行),并且互不影响。

两种学派的对比

伦敦派:

  • 关注代码单元,粒度更细
  • 所有依赖都应该使用测试提替身

经典派:

  • 过分关注代码单元没有意义,应该聚焦业务价值(功能)
  • 测试应该面向功能,而非代码单元
  • 依赖关系复杂意味着设计不合理

两种学派在做TDD时也截然不同。伦敦派可以自顶向下做TDD,经典派只能自底向上做TDD

方法论

实施策略

  • 单元测试伴随整个代码开发过程
  • 单元测试只针对最有价值的代码
  • 单元测试提供最高的性价比

编写原则

  • F(Fast):测试用例要能够简单快速的运行起来
  • I(Isolate):测试用例要尽量独立,不能够相互影响或依赖
  • R(Repeatable):测试用例要能够重复运行
  • S(Self-verifying):测试用例要能够自己检测输出,判断结果
  • T(Timely):测试用例要及时写

构建模式

使用AAA模式,将单元测试分为三个部分:arrangeactassert

  • arrange:将SUT和其依赖设置到指定状态
  • act: 执行SUT的方法,获取输出
  • assert: 通过断言验证输出结果

实用建议

  1. 单元测试代码不应该包含任何分支语句
  2. 尽量避免多个arrange,actassert
  3. act部分应当只有一行代码
  4. arrange部分应当是代码量最多的部分
  5. 将被测方法命名为sut

反例:

  1. 不要测试私有方法
  2. 不要暴露和检测sut的内部状态
  3. 不要mock具体实现类
  4. 不要让测试代码污染业务代码

健壮性

单元测试的核心作用:

  1. 当代码存在bug时,单元测试应当能够发现bug
  2. 当代码没有bug时,单元测试应该显示没有bug
  3. 检测应当迅速且容易实施和维护

 这里可以把单元测试看做是对人进行核酸检测。把人看做是被测的代码单元,把核酸检测看做是单元测试代码的执行。上面对单元测试的要求可以对应以下三个对核酸检测的要求:

  1. 如果一个人阳了,核酸检测却显示阴性,则核酸检测无法正确检出小阳人,此时出现假阴性。

  2. 如果一个人没阳,核酸却显示阳了,则核酸检测误报,此时出现假阳性。

  3. 要降低核酸检测的成本,如果核酸成本高,就难以及时检测,也难以大规模检测,也就无法及时发现小阳人了

1.和2 两种情况我们都不希望出现,但是应该杜绝假阴性,可以容忍假阳性。假阴性导致的后果很严重(无法检出小阳人,会导致病毒扩散),假阳性不会向假阴性一样引起那么严重的后果,但同样会降低人们对核酸的信任(单元测试出现假阳性,开发人员会觉得单元测试代码不靠谱,那么在开发中就不会执行单元测试了)。

单元测试代码应当抑制功能退化:防止假阴性。

  好的单元测试可以很好的确保新特性不会影响旧功能。

  具体来说,sut的代码量越大、复杂度越高,业务价值越大,添加新特性、新功能时影响旧功能的可能性越大。单元测试应当聚焦这些代码,而非边缘样板代码。在对这些代码做单元测试时,设计出的单元测试代码应当尽可能的检测出这些业务中的bug,尽量避免代码中有bug,测试代码却没有检测出来的情况.特别是在对旧功能添加新特性的时候,如果新增的代码影响了旧有的功能,单元测试应当立即能够检测出来问题。

单元测试代码应当免疫重构:可以防止假阳性

  好的单元测试应当增加重构自信,它只会在重构破坏了原有功能时不通过,在没有破坏原有功能时正常通过。如果经常出现假阳性,开发人员会对单元测试失去信心。

快速和易维护

  单元测试应当能够快速执行并给出结果(外部依赖很少)。

  单元测试代码应当易于维护(易于理解)。

存不存在能够同时满足以上三点要求的单元测试?答案是不存在那么完美的单元测试。我们只能做抉择。

 

  1. 不出现假阳性(对重构免疫)是基本要求
  2. 在检测速度与灵敏度之间做抉择l

黑盒测试

 

黑盒测试:在不知道软件内部结构和实现的情况下,对软件的功能进行测试。只关心软件的功能是否正确实现,不关心软件的功能是如何实现的。

白盒测试:与黑盒测试相反,了解软件内部实现的情况下,对其内部实现进行测试验证,更关心软件的内部行为。

 

白盒测试:更全面,但与代码实现耦合更紧密,更容易出现假阳性。黑盒测试:没有白盒测试脆弱和敏感,抑制假阳性方面做的更好,测试灵敏度又在可接受范围。

单元测试推荐使用黑盒测试

Mock

Mock vs Stub

  Mock用于模拟和验证输出方向的交互。Stub用于模拟输入方向的交互。尽量不要验证sutstub的交互,一旦验证意味着验证了实现细节,而不是功能。

Spy vs Mock

  框架自动生成的叫Mock,自己手动创建的叫Spy

依赖

  受控依赖:只被自己系统使用的依赖,自己有完全控制权

  非受控依赖:各系统共享的依赖,自己没有完全控制权

 

验证

好的测试代码应该只验证sut的功能,而非其实现细节。亦即验证sut做了什么,做对了没有,而不是验证它是怎么做的。如何区分功能和实现细节?

所有的代码都可以分为两类:公共API和私有API。公共API通过暴露函数或者状态给调用者,帮助其实现目标。私有API则不被调用者感知。暴露给使用者的公共API是可以并且需要验证的,其他的都不应该去验证。

为了使单元测试能够做到验证行为,不与实现细节耦合,需要业务代码具有良好的设计。业务代码需要合理的封装,将实现细节隐藏起来,只暴露能够帮助使用者实现其目标的API和状态,并确保这些API和状态不会违反一致性。好的单元测试与好的设计有着天然的联系。

验证的三种途径:

  • 验证结果
  • 验证状态
  • 验证行为

验证结果的单元测试具有更好的重构免疫力,验证行为的单元测试重构免疫力最差。

集成测试

集成测试关注代码与外部依赖(进程外的依赖)一起工作时的状态。

特征:

  • 测试的代码范围更广
  • 对外部有依赖
  • 与业务代码耦合性更低,防止功能退化能力更强
  • 集成测试专注于代码与其依赖一起工作能否成功

 

 

 

 

集成测试用于测试controller,单元测试用于测试业务逻辑。

集成测试在防止功能退化和免疫重构方面做得更好,而单元测试更快速且更容易维护。

集成测试应当更关注单元测试做不到的地方:单元测试尽可能多的进行边界测试,集成测试尽可能少的进行边界测试。集成测试应当尽可能多的关注sut和其依赖能否协作,因此集成测试的功能需要把所有的依赖都贯穿进来。

参考资料

  1. Manning.Unit.Testing.Principles.Practices.and.Patterns.
  2. Practical-Unit-Testing-with-JUnit-and-Mockito
  3. https://junit.org/junit5/docs/current/user-guide/
  4. https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html
  5. https://hamcrest.org/JavaHamcrest/tutorial
  6. https://docs.spring.io/spring-boot/docs/2.7.12/reference/htmlsingle/#features.testing
  7. https://howtodoinjava.com/spring-boot2/testing/

 

posted @ 2023-09-12 20:34  小张同学哈  阅读(111)  评论(0编辑  收藏  举报