重构三部曲(二):单元测试篇

前言

接上篇 重构三部曲(一):思想准备篇

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

关键步骤如下图

image.png
选择JUnit版本

代码覆盖率工具-Jacoco

搜索EclEmma
image.png

注解

注解 用途
@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));
    }
}
执行结果

参考网站

JUnit 5 新特性
JUnit 5 User Guide
Jacoco+Eclipse简单操作

你可能感兴趣的:(重构三部曲(二):单元测试篇)