SpringBoot学习小结之测试

前言

在这里插入图片描述
测试包含以下四种类型

  1. 单元测试(Unit Test)

    指对程序中的最小可测试单元进行验证。进行单元测试,可以尽早地发现编写代码中错误,减少后期测试开销和维护成本,提高软件质量

  2. 集成测试(Integration Test)

    集成测试是在单元测试的基础上,将所有已通过单元测试的模块按照概要设计的要求组装为子系统或系统,并进行测试的过程。实践表明,一些模块虽然能够单独地工作,但并不能保证连接起来也能正常的工作。一些局部反映不出来的问题,在全局上很可能暴露出来

  3. 系统测试(System Test)

    系统测试是将经过集成测试的系统或子系统,与系统中其他部分结合起来,在实际运行环境下对系统进行的一系列严格有效地测试,以发现软件潜在的问题,保证系统的正常运行。

  4. 验收测试(Acceptance Test)

    验收测试是部署软件之前的最后一个测试操作。它是技术测试的最后一个阶段,也称为交付测试。验收测试的目的是确保软件准备就绪,按照项目合同、任务书、双方约定的验收依据文档,向软件购买方展示该软件系统满足原始需求。

四种测试各个方面区别

单元测试集成测试系统测试验收测试
测试阶段编码后或编码前单元测试通过后集成测试通过后系统测试通过后
测试对象最小模块模块间的接口,多模块组成的子系统整个系统整个系统
测试内容模块接口测试、局部数据结构、路径测试、错误处理测试、边界测试模块之间数据传输、模块之间功能冲突、模块组装功能正确性、全局数据结构、单模块缺陷对系统的影响功能、界面、可靠性、易用性、性能、兼容性、安全性等同系统测试
测试人员开发人员开发人员测试人员需求方或最终用户
测试方法白盒测试黑盒测试+白盒测试黑盒测试黑盒测试

一、单元测试

1.1 基本原则

​ 一个好的单元测试应该具备以下FIRST 原则和AIR原则中的任何一条

  • FIRST 规则
    • Fast 快速原则,测试的速度要比较快
    • Independent/Isolated 独立/隔离原则,每个测试用例应该互不影响,不依赖于外部资源
    • Repeatable 可重复原则,同一个测试用例多次运行的结果应该是相同的
    • Self-validating 自我验证原则,单元测试可以自动验证,并不需要手工干预
    • Thorough/Timely 及时原则 单元测试必须及时进行编写,更新,维护。保证测试用例随着业务动态变化
  • AIR原则
    • Automatic 自动化原则 单元测试应该是自动运行,自动校验,自动给出结果
    • Independent 独立原则 单元测试应该独立运行,相互之间无依赖,对外无依赖,多次运行之间无依赖
    • Repeatable 可重复原则 单元测试是可重复运动的,每次的结果都稳定可靠

一个整套完善的单元测试可以保障后续的增添功能时,程序迭代过程中,代码的逻辑正确性。验证程序的输入和输出与最初设计一致。这对后续的集成测试等会提供巨大的帮助。同时也会有利于集成测试的顺利进行

1.2 粒度

在使用 springboot 开发的项目中,可以考虑以下粒度做测试

  1. DAO: 对于框架提供的基本CRUD,可以考虑跳过这一部分单元测试,而一些比较复杂的动态更新、查询等操作,建议用使用H2去做模拟单元测试
  2. Service:基本上一个service里面肯定会依赖很多其他的service(此处也建议将成员变量通过构造方法进行注入,以便于单元测试去Mock),此时建议我们将依赖其他service的方法用Mock替代,Service里面的一些数据库的操作也进行Mock。这样可以保证service测试的独立性,不过对于逻辑复杂的方法可能要花很多时间在Mock上面。 如果发现需要Mock的方法过多,那么可能就需要考虑将要测试的方法是不是需要重构
  3. Controller(API):主要着重测试HTTP status在 200,400,500 等情况下的异常处理,request及response的转换等。由于其余部分的代码测试都已经在其对应的单元测试覆盖,那么此时可以Mock绝大部分Serivce层中的方法
  4. 工具类:一些工具类里面包含了比较多的逻辑,所以需要尽可能考虑多种情况下测试用例

1.3 JUnit

JUnit,一个由Kent Beck和Erich Gamma于1997年建立的Java单元测试框架,现在主要使用的有两个版本JUnit4JUnit5

  1. JUnit4

    • 常用注解,位于org.junit包下面

      注解名含义
      @After每个测试用例之后都将调用@After注解方法
      @AfterClass在测试类中的所有测试方法之后执行。注解在静态方法上
      @Before每个测试用例之前都将调用@Before注解方法
      @BeforeClass在测试类中的所有测试方法之前执行。注解在静态方法上
      @Test标记方法为测试方法
    • 断言方法,位于org.junit.Assert类内

      • assertTrue
      • assertFalse
      • assertEquals
      • assertNotEquals
      • assertSame
      • assertNotSame
      • assertThrows
  2. Junit5

    • 常用注解,位于org.junit.jupiter.api包下面

      注解名含义
      @AfterEach每个测试用例之后都将调用@AfterEach注解方法
      @AfterAll在测试类中的所有测试方法之后执行。注解在静态方法上
      @BeforeEach每个测试用例之前都将调用@BeforeEach注解方法
      @BeforeAll在测试类中的所有测试方法之前执行。注解在静态方法上
      @Test标记方法为测试方法
      @Timeout运行超过指定的时间的话,就会自动报错
      @RepeatedTest重复执行n次
      @ExtendWith为测试类或测试方法提供扩展类引用
    • 断言方法,位于org.junit.jupiter.api.Assertions

      • assertTrue
      • assertFalse
      • assertNull
      • assertNotNull

1.4 Hamcrest

Hamcrest是一个匹配器,可以用来灵活创建、组合表达式进行断言。

在junit自带的断言中,我们会发现都比较简单,如果碰到稍微复杂的,比如判断数组中是否有某个值,就没有对应的断言方法。而Hamcrest内部提供了很多常用的匹配器,可用来实现断言

使用Hamcrest可导入静态方法

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
  • 核心

    • anything - 始终匹配,如果你不关心被测对象是什么,则很有用

      assertThat("123", is(anything())); 
      
    • describedAs - 装饰器,添加失败自定义描述

      assertThat("3", describedAs("a num equal to %0", equalTo(3), 3));
      //Expected: a num equal to <3>
      //     but: was "3"
      
    • is - 装饰器,用来提高可读性

      assertThat(123, is(equalTo(123)));
      assertThat(123, equalTo(123));
      // 两者等价
      
  • 逻辑

    • allOf - 如果所有匹配器匹配,则匹配(像 Java中的 &&)

      assertThat( Arrays.asList("1x", "2x", "3x", "4z"), allOf(isA(List.class), hasSize(4)));
      
    • anyOf - 如果有匹配器匹配,则匹配(像Java中 ||)

      assertThat("123", anyOf(isA(Integer.class), equalTo("234")));
      
    • both 两个匹配器都匹配则匹配

      assertThat("123", both(isA(String.class)).and(is(equalTo("123"))));
      
    • either 两个匹配器任意一个匹配则匹配

      assertThat("123", either(isA(String.class)).or(is(equalTo("123"))));
      
    • not - 如果包装的匹配器不匹配,则匹配,反之亦然

      assertThat("123", not(isA(Integer.class)));
      
    • everyItem 所有元素匹配则匹配

      assertThat( Arrays.asList("1x", "2x", "3x", "4z"), everyItem(endsWith("x")));
      
  • 对象

    • equalTo - 使用 对象的equals方法测试是否相等

      assertThat(123, equalTo(123));
      
    • hasToString - 测试对象的toString

      assertThat(true, hasToString("TRUE"));
      
    • instanceOf - 测试是否类型为给定值

      assertThat("123", instanceOf(String.class));
      
    • isA instanceOf的简写

    • notNullValue, nullValue - 测试是否为null

      assertThat("123", notNullValue());
      assertThat(null, nullValue());
      
    • sameInstance -测试对象是否相同

      assertThat(127, sameInstance(127));
      assertThat(128, sameInstance(128));
      
  • Beans

    • hasProperty - 测试JavaBeans 是否有某个属性

      @Data
      class Demo {
          private String str;
      }
      
      assertThat(new Demo(), hasProperty("str"));
      
  • 集合

    • array - 根据匹配器数组测试数组的元素

      assertThat(new Integer[]{1,2,3}, is(array(equalTo(1), equalTo(2), equalTo(3))));
      
    • hasEntry, hasKey, hasValue - 测试map是否包含Entry、键或值

      Map map = Maps.newHashMap("foo", "bar");
      assertThat(map, hasEntry("foo", "bar"));
      assertThat(map, hasKey("foo"));
      assertThat(map, hasValue("bar"));
      
    • hasItem, hasItems - 测试集合是否包含值

      assertThat(Arrays.asList("foo", "bar"), hasItem("bar"));
      assertThat(Arrays.asList("foo", "bar"), hasItem(startsWith("f")));
      assertThat(Arrays.asList("foo", "bar"), hasItems("foo", "bar"));
      
    • hasItemInArray - 测试数组包含元素

      assertThat(new Integer[] {1,2,3}, hasItemInArray(4));
      assertThat(new String[] {"foo", "bar"}, hasItemInArray(startsWith("ba")))
      
    • hasSize 测试集合Size是否为给定值

      assertThat(Arrays.asList("foo", "bar"), hasSize(4));
      
    • empty 测试集合是否为空

      assertThat(Arrays.asList("foo", "bar"), empty());
      
    • contains 测试集合是否包含给定值

      assertThat(Arrays.asList("foo", "bar"), contains("f"));
      
    • in 测试给定值是否包含在集合里

      assertThat("foo", in(new String[]{"foo", "f", "bar"}));
      
  • 数字

    • closeTo - 测试浮点值是否接近给定值

      assertThat(1.03d, is(closeTo(1.0d, 0.03d)));
      
    • greaterThan, greaterThanOrEqualTo, lessThan, lessThanOrEqualTo - 测试大小

      assertThat(1.03, is(greaterThan(1.0d)));
      
  • 字符串

    • equalToIgnoringCase - 忽略大小写情况下,测试字符串是否相等

    • containsString, endsWith, startsWith - 测试字符串是否包含给定值,结束于给定值,起始于给定值

    • blankString 测试是否空字符串

    • hasLength 测试是否长度为给定值

二、集成测试

集成测试可以是以下任何一种:

  • 涵盖多个“单元”的测试。它测试两个或多个类之间的相互作用

  • 覆盖多层的测试。这实际上是第一种情况的特殊化,可能涉及到业务层和持久层之间的交互

  • 覆盖整个应用程序路径的测试。在这些测试中,我们向应用程序发送请求,并检查它是否正确响应,以及是否根据我们的期望更改了数据库状态

Spring Boot为集成测试提供了@SpringBootTest注解,我们可以使用该注解创建一个应用程序上下文,其中包含上述所有测试类型所需的所有对象。然而,请注意,过度使用@SpringBootTest可能会导致测试运行时间过长

因此,对于覆盖多个单元的集成测试,我们应该创建简单测试,非常类似于单元测试,在测试中,我们手动创建测试所需的对象,并模拟其余的对象。这样,Spring就不会在每次启动测试时触发整个应用程序上下文

有以下注解可以帮助我们不必启动整个Spring上下文

  • @WebMvcTest 测试controller层

  • @DataJpaTest 测试jpa持久层

三、Springboot中的测试框架

3.1 测试框架

当引入spring-boot-starter-test时,以下测试相关库SpringBoot都支持

  • JUnit: Java应用程序单元测试的事实上的标准

  • Spring Test & Spring Boot Test: Spring Boot应用程序的集成测试支持

  • AssertJ: 一个流式断言库

  • Hamcrest: 一个匹配器库

  • Mockito: 一个Java Mock框架

  • JSONassert: 一个JSON的断言库

  • JsonPath: 一个读取json文档的DSL

3.2 JUnit版本

​ 由于SpringBoot版本不同,支持的JUnit版本也不同。

  • Spring Boot版本>2.4.0,那么只包含JUnit 5 Jupiter版本

  • 2.2.0 <Spring Boot版本 < 2.4.0,包含了JUnit5和4,默认使用了 JUnit 5 ,需要在pom文件中去除JUnit4

  • Spring Boot版本 < 2.2.0 只包含JUnit4

3.3 Mockito

Mockito是一个用 Java 开发,功能强大的模拟测试框架, 通过 Mockito 我们可以创建和配置 Mock 对象, 进而简化有外部依赖的类的测试。

Mockito可以用来模拟方法的返回值,当service层需要调用dao层的一个方法时,可以用mockito来模拟dao层方法,而不是真正的连接数据库执行dao层方法,这就符合单元测试的原则:快速,无依赖

class UserServiceTest {

    @InjectMocks
    private UserServiceImpl userService;

    @Mock
    private UserDao userDao;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.initMocks(this);
    }


    @Test
    void testFindUserRoles() {
        Role role1 = Role.builder().id(1).name("管理员").build();
        Role role2 = Role.builder().id(1).name("前端组").build();
        List<Role> roleList = Lists.list(role1, role2);

        when(userDao.findUserRoles(anyInt())).thenReturn(roleList);

        assertThat(userService.findUserRoles(1), is(equalTo(roleList)));
    }
}

参考

  1. https://docs.spring.io/spring-boot/docs/2.1.9.RELEASE/reference/htmlsingle/#boot-features-testing
  2. SpringBoot Test 人类使用指南
  3. SpringBoot项目中单元测试与集成测试的应用
  4. https://junit.org/junit5/docs/current/user-guide/junit-user-guide-5.8.2.pdf
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

aabond

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值