SpringBoot开发中的一些小细节(十二)单元测试

SpringBoot开发中的一些小细节(十二)单元测试

2020, Jan 10    

前言

2020将近,已经很久没有更新blog,但是每天的工作笔记都还是有在记录,趁着马上就要过年,没有那么忙碌了,把自己19年Q4的工作中遇到的问题以及一些有用的方法整理一下~

测试的必要性

在程序员的日常工作中,实际上真正在编写代码的时间,在整个工作时间中所占的比例其实很小的一部分。在写代码之前我们需要撰写需求文档,以及充足的思考,画出uml图(虽然很多经验丰富的大佬都没有画,但是那是建立在经验相当丰富的基础之上的);在写完代码之后,这个项目需要发布与部署到测试环境,这就是CI/CD所负责的部分了;再之后,我们就需要测试我们的项目,是否能够完成需求文档中的需求,实现了相应的功能。 如果没有进行充分完整的测试,直接上线,这个项目差不多就是穿着皇帝的新衣在大街上到处晃荡了。同样,在互联网高速发展的今天,测试也不再仅仅是人工进行测试,自动化测试的演进又再一次的提升了团队的生产效率。这和敏捷开发、持续集成、持续部署、DevOps是一脉相承的。

测试金字塔Test Pyramid

测试金字塔

图来源于ThoughtWorks

在上方的金字塔当中,越往金字塔尖尖方向的,集成的程度越高,测试所耗费的时间就越长,但是需要的测试数目并没有那么多;与之相反,越往金字塔底层方向的,集成的程度就越低,测试所消耗的时间也更快,但是所需要测试的数量就会更加的庞大。

  • UI Tests 项目的ui界面测试,这一层面的测试比较适合在上线之后进行冒烟测试,能够知道项目的整个流程到底是不是有问题。但是通常由于集成的程度太高,不能很快的定位问题。
  • Service Tests 这一层,用来测试服务是否可用,比如API测试。
  • Unit Tests 在代码层面进行测试,通常可以针对某一个类,某一个方法进行测试,能够快速定位问题。这个层级的层级能够保证一个系统的每一个基石都能够按照预期的那样进行工作。单元测试的数量应该在测试组合中远远多于其他类型的测试。

Junit

三段式结构

我们通常采用三段式的结构来撰写单元测试

  • 构造部分。进行对象的构建、赋值等。
  • 逻辑部分。运行方法逻辑对第一部分的数据对象进行运算。
  • 断言部分。判断本次单元测试能否通过。

实例代码

要使用is()方法首先需要导入hamcrest-library-1.3.jar和hamcrest-core-1.3.jar两个jar包

依赖配置:

<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-library</artifactId>
    <version>1.3</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

简单的UT代码实现

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;


@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class ApplicationTests {
    
    @Test
    @Ignore
    public void contextLoads() {
    }

    public Boolean compare(String A, String B) {
        return A.equals(B);
    }

    @Test
    public void testCompare() {
        // 第一段
    	var tempParamA = "1";
        var tempParamB = "2";

        // 第二段
        var result = compare(tempParamA, tempParamB);

        // 第三段
		assertThat( result, is(false));
    }
}

上述代码的意思是

  • 定义两个参数AB
  • 使用compare方法对两个参数进行比较
  • 断言,如果result是false,那么本次UT通过

其他需要注意的地方

  1. 通常在进行打包的时候,一般是会跳过这个打包阶段的mvn clean package -DskipTests,当然如果我们想要执行单元测试,那么直接mvn clean package这样就是ok的。
  2. 在写单元测试的时候,我们需要将语义表达清楚,尤其是在写单元测试名字的时候,还有在写断言的时候,需要直接就是英文的一句具有完整语义的句子,这样在后期维护的时候会很方便,可以一眼就知道这是干什么的。
  3. 在不想跑的Test上面加上@Ignore就可以不执行我们不需要的依赖等等,因此我们最好就是使用充血模型,在每个model中最好就有本类能做到的相关功能的方法实现。(贫血模型,通常就是简单的POJO,主要的功能方法都是在Service等外界实现的)