前言
接上篇 重构三部曲(一):思想准备篇
PS:本文主要知识来源于《Java测试驱动开发》这本书
理论知识储备
关于单元测试
- 单元测试并非要取代其他类型的测试,而只是缩小其他测试的范围
- 单元测试旨在检查代码的内部质量,专注于质量保证而非质量检查
- 质量检查(QC)的重点是发现缺陷
- 质量保证(QA)的重点是将缺陷消灭在萌芽状态
- 单元测试必须能够快速运行
- 必须明确指出测试前设置了哪些条件,测试将执行哪些操作以及期望的结果。
- 不要让测试依赖于其他测试
- 应尽可能在测试中少用甚至不用基类。相对比避免代码重复,测试的清晰度更重要
- 一种命名方式:Given部分描述前置条件,When部分描述操作,Then部分描述期望的结果
- 测试必须因预期的原因而失败。
关于TDD(测试驱动开发)
- 概念说明:TDD这本书编写的测试不以Test结尾,而以Spec结尾,以规范为目标
- 规范不仅用于验证代码,还被用作可执行的文档,最主要的是,它们还被用作思考和设计方式。
- 红灯-绿灯-重构(失败到成功再到完美)
- 第一次运行不能通过:还没有创建方法
- 第二次运行不能通过:添加了方法,但是没有实现
- 第三次运行通过:实现了与这个测试相关联的所有代码
- TDD迫使我们详细地考虑需求和设计,编写整洁而可行的代码,以及创建可执行的需求并频繁重构。
- 以测试方式编写的文档。测试就是可执行的文档,而TDD是创建和维护这种文档的最常用方式。
- 需要为代码编写文档通常意味着代码本身写的不好。另外,不管你如何努力,文档都必然会过期。
- 实现越简单,产品越好,维护也越容易
- 使用TDD时,不用预先定义设计,相反,不断编写并实现规范的过程中,设计通常会变得清晰(熟练之后的事,熟练掌握TDD之前,我们必须将需求和测试分开定义)
- 将TDD过程分解为可重复的短暂周期,其中每个阶段的持续时间通常以分钟乃至秒计
- 单元测试的运行时间多长算合理呢?没有放之四海而皆准的规则,但一条经验是:如果时间超过10~15秒,就应对此感到担忧,并花时间对测试进行优化。
关于Mockito
- 将真实类转换为模拟类。就像重写了这个类,将其所有方法都改为空。
- mock():用于创建模拟对象,还可使用when()和given()指定这些模拟对象的行为
- spy():可用于实现部分模拟。除非另有说明,否则间谍对象调用实际方法。mock()创建一个完全伪造的对象,而spy()使用实际对象
- verify():用于检查调用方法时提供的是否是指定参数,是一种断言。
Eclipse 中JUnit 5的使用
项目右键 -> properties -> Java Build path -> Add Library -> Junit -> JUnit 5 -> finish
关键步骤如下图
代码覆盖率工具-Jacoco
注解
注解 | 用途 |
---|---|
@Test | 表明一个测试方法 |
@DisplayName | 测试类或方法的显示名称 |
@BeforeEach | 表明在单个测试方法运行之前执行的方法 |
@BeforeAll | 表明在所有测试方法运行之前执行的方法 |
@AfterEach | 表明在单个测试方法运行之后执行的方法 |
@AfterAll | 表明在所有测试方法运行之后执行的方法 |
@Disabled | 禁用测试类或方法 |
@Tag | 为测试类或方法添加标签 |
简单示例
class FirstTDDSpec {
private FirstTDD testd;
@BeforeAll
static void initAll() {
System.out.println("@BeforeAll 初始化……");
}
@BeforeEach
void beforeEachTest() {
System.out.println("@BeforeEach 初始化……");
testd = new FirstTDD();
}
@AfterAll
static void destoryAll() {
System.out.println("@AfterAll 所有测试执行完毕,执行销毁操作……");
}
@AfterEach
void detory() {
System.out.println("@AfterEach 当前测试执行完毕,执行销毁操作……");
}
@Test
void whenTheGameIsStartedTheBoardIsEmpty() {
Assert.assertEquals(0, testd.getNumberOfDiscs());
}
@Test
@DisplayName("第一个mock测试方法")
void MyFirstMockTest() {
HelloModel mockHelloModel = Mockito.spy(HelloModel.class);
mockHelloModel.setUserName("jack");
mockHelloModel.setPassword("123456");
Assert.assertEquals("jack", mockHelloModel.getUserName());
}
@Test
void whenDiscOutsideBoardThenRuntimeException() {
int column = -1;
assertThrows(RuntimeException.class, () -> testd.putDiscInColumn(column));
}
}