什么是优秀的测试
测试的价值
- 测试帮助捕获错误,因此一个永远正确的测试是一个无效的测试,而一个永远错误的测试也不能提供有效的信息.
- 在另一个层面,测试提供一个实际使用的环境,从而明确实际的需求并给出辅助设计
- 编写测试的最大价值不在于结果,而在于编写过程的学习
评价测试的标准
- 可读性, 较少阅读代码的难度, 从而提供生产力
- 测试结果的准确性, 不要让错误抵消了测试带来的好处
- 可信赖性和可靠性
测试的潜力
- 将测试作为一种设计工具,指导代码对实际用途的设计,从而开发过程变为
测试->代码->重构
的循环 - 首先给出测试,之后以测试为依据写出能通过测试的代码, 最后重构代码.
测试替身
测试替身的作用是将被测代码与周围隔开,使测试不依赖随机数等外部因素, 从而将执行变得确定. 此外使用测试替身还可以模拟特殊的场景, 或者暴露特定的信息.
替身的类型
类型 | 使用情景 |
---|---|
测试桩(Stub) | 不关心替身的实现时, 使用测试桩 |
伪造对象(Fake) | 在需要某些情况下能特异性的返回不同结果时, 使用伪造对象. |
测试间谍(Spy) | 当需要获得一些内部信息时, 使用测试间谍 |
模拟对象(Mock) | 需要对替身进行精细的控制时, 使用模拟对象 |
具体使用哪一种替身取决于具体的需求:
- 如果关心交互情况, 考虑使用Mock
- 如果使用Mock的结果不如预期, 可以考虑使用一个Spy
- 如果只关心替身向被测对象发送的相应, 可以使用Stub, 或者使用简单的Mock
- 如果运行于一个复杂的场景, 而且不能简单的使用Stub, 可以考虑使用Fake
测试桩(Stub)
桩都是简单的, 通常只是硬编码的返回一个结果或者完全就是空的方法. 在这种情况下, 我们通常不关心替身如何实现, 也不希望因为替身的实现逻辑消耗太多时间和资源
1 | public class LoggerStub implements Logger { |
伪造对象(Fake)
伪造对象像一个真实事物的简单版本, 优化的伪造真实事物的行文, 比测试桩更加真实. 持久化对象是使用Fake的典型场景, 由于数据访问会消耗很多时间, 而且操作有副作用, 因此不应该使用真实的数据库.
在访问文件或者数据库时, 使用一个Fake就相当于自己实现了一个自定义内存数据库. 例如, 对于下面这个接口定义的数据库发方法
1 | public interface BookRepository { |
可以通过以下这个Fake简单的实现数据库的有关操作.
1 | public class FakeBookRepository implements BookRepository { |
测试间谍(Spy)
测试间谍继承需要替换的类,从而可以将一些内部数据通过额外的方法暴露给外部
1 | public class DLogTest { |
模拟对象(Mock)
模拟对象除了保证方法可以被特异性的调用以外, 还可以对调用次数做出规定, 从而任何条件不满足时都能抛出异常. 对于模拟对象, 可以使用JMock, Mockito等模拟对象库. 关于模拟对象的使用可以阅读Java单元测试库简介 的JMock章节.
Given, When, Then
给定-当-那么
是一种组织测试方法的约定, 一个测试方法可以分成三个部分, 使用这种表达是希望我们在编写测试的过程中能够关注于行为, 而不是程序实现的细节. 下面是一个示例
1 |
|
注意, 一定要避免对Mock对象设置过于详细的期望, 如果太详细, 会导致程序的灵活性降低, 细小的变更也会导致测试的错误. 测试用例应该检测程序的行为, 而不是程序的实现.
参考文献
最后更新: 2024年10月19日 22:36
版权声明:本文为原创文章,转载请注明出处