Junit5单元测试

单元测试

单元测试是代码正确性验证的最重要的工具,也是系统测试当中最重要的环节。在标准的开发过程中,单元测试的代码与实际程序的代码具有同等的重要性。

好处

1、单元测试在实际开发中代替main方法验证内部类的正确性。
2、提高代码质量和可维护性,保证模块修改后的兼容性。可以通过单元测试来判断一个模块在修改后是否与修改前保持兼容、多大程度上不兼容、影响范围有多大。

JUnit5

与以前版本的JUnit不同,JUnit 5由三个不同子项目中的几个不同模块组成。

  • Platform:位于架构的最底层,是JVM上执行单元测试的基础平台,还对接了各种IDE(例如IDEA、eclipse)或构建工具,控制台进行单元测试,并且还与引擎层对接,定义了引擎层对接的API;
  • Jupiter:位于引擎层,支持JUnit 5版本的编程模型、扩展模型;
  • Vintage:位于引擎层,实现向后兼容性,执行低版本的测试用例,即JUnit 5可以使用Vintage库运行JUnit 4测试,兼容旧版本单元测试。

优势

  • 提供全新的断言和测试注解,支持测试类内嵌

  • 更丰富的测试方式:支持动态测试,重复测试,参数化测试等

  • 实现了模块化,让测试执行和测试发现等不同模块解耦,减少依赖

  • 提供对 Java 8 的支持,如 Lambda 表达式,Sream API等。

迁移指南

JUnit 平台可以通过 Jupiter 引擎来运行 JUnit 5 测试,Vintage 引擎来运行 JUnit 3 和 JUnit 4 测试。因此,已有的 JUnit 3 和 4 的测试不需要任何修改就可以直接在 JUnit 平台上运行。只需要确保 Vintage 引擎的 jar 包出现在 classpath 中,JUnit 平台会自动发现并使用该引擎来运行 JUnit 3 和 4 测试。开发人员可以按照自己的项目安排来规划迁移到 JUnit 5 的进度。可以保持已有的 JUnit 3 和 4 的测试用例不变,而新增加的测试用例则使用 JUnit 5。

SpringBoot版本依赖

1、当前父pom中SpringBoot版本为2.3.8.RELEASE,

    <parent>
        <groupId>com.**.cloud</groupId>
        <artifactId>**-starter-parent</artifactId>
        <version>1.2.0</version>
    </parent>

2、JUnit5的jar都被spring-boot-starter-test间接依赖进来。

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>

在这里插入图片描述

注意事项

  • 一个service对应一个test;
  • 一个方法对应多个test
    • 正向测试
      • 这种测试前期任务是要准备足够的正确数据(前提是要保证数据的正确性,这个很重要),运行代码后返回的值或产生的影响是要跟自己的预期是一致的!
    • 异常输入
      • 边界条件数据,比如值类型数据的最大值、最小值、DBNull,或者是方法中所使用的条件边界,例如a>100那么100就变成了这个数据的边界。而且在测试的时候还必须把超出边界的数据作为测试条件进行测试。

      • 空数据,一般空数据对应于引用类型的数据,也就是Null值。

      • 格式不正确数据,对于引用类型的数据或者结构对象,类型虽然正确但是其内部的数据结构不正确的数据。例如一个数据库实体对象,数据库中要求其某个属性必须为非空,但是这时我们可以设置为空进行测试。

  • 每个特性只测一次,在测试模式下,有时会情不自禁的滥用断言。这种做法会导致维护更困难,需要极力避免。仅对测试方法名指示的特性进行明确测试。对于一般性代码而言,保证测试代码尽可能少是一个重要目标。
  • 使用显式断言,应该总是优先使用“assertEquals(a, b)”而不是“assertTrue(a == b)”,因为前者会给出更有意义的测试失败信息。在事先不确定输入值的情况下,这条规则尤为重要,比如之前使用随机参数值组合的例子。

测试方法命名

在单元测试中我们尽量少些注释,以至于不写,那么我们就要写出易读的测试名称,建议可以采取 “give操作对象+when测试对象+then断言” 的模式。

  • 测试对象:将要测试的对象,即方法名称AddUser,DeleteUser等;
  • 操作对象:将要对这个对象进行什么样的操作,比如有效的用户名,无效的用户名等;
  • 断言:对结果做出判断,比如这个操作会抛异常,这个操作正常,这个操作会失败,这个值会发生改变等。

例:
当添加一个有效的用户的时候操作成功

addUser_ShouldSuccess_WhenGivenValidUserInfo()

当删除用户时该Id用户不存在抛出异常。

deleteById_ShouldThrows_WhenGivenIdNotExist

常用注解

ExtendWith:

在不同的Spring Boot版本中@ExtendWith的使用也有所不同,其中在Spring boot 2.1.x之前, @SpringBootTest 需要配合@ExtendWith(SpringExtension.class)才能正常工作的。

而在Spring boot 2.1.x之后,我们查看@SpringBootTest 的代码会发现,其中已经组合了@ExtendWith(SpringExtension.class),因此,无需在进行该注解的使用了。如果对扩展性有更多需求,可以添加@ExtendWith注解。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(SpringBootTestContextBootstrapper.class)
@ExtendWith({SpringExtension.class})
public @interface SpringBootTest {

Mock

对函数的调用均执行mock(即虚假函数),不执行真正部分,默认返回null.

InjectMocks

执行注入mock对象的操作,可以调用真实代码的方法,在 setUp方法中 执行 MockitoAnnotations.initMocks(this); 的时候,会将标记了 @Mock 或 @Spy 的属性注入到 service 中。

Spy

Spy声明的对象,对函数的调用均执行真正部分。

Test:

注解在方法上标记方法为测试方法,以便构建工具和 IDE 能够识别并执行它们。JUnit 5不再需要手动将测试类与测试方法为public,包可见的访问级别就足够了。

BeforeAll:

被该注解修饰的必须是静态方法,只执行一次,执行时机是在所有测试和 @BeforeEach 注解方法之前,会被子类继承,取代低版本的BeforeClass;

AfterAll:

被该注解修饰的必须是静态方法,执行时机是在所有测试和 @AfterEach 注解方法之后,会被子类继承,取代低版本的AfterClass;

BeforeEach:

被该注解修饰的方法会在每个测试方法执行前被执行一次,会被子类继承,取代低版本的Before;

AfterEach:

被该注解修饰的方法会在每个测试方法执行后被执行一次,会被子类继承,取代低版本的After;

DisplayName:

测试方法的展现名称,在测试框架中展示,支持emoji;

Timeout:

超时时长,被修饰的方法如果超时则会导致测试不通过;

Disabled:

不执行的测试方法;

Mockito框架

打桩测试

Mockito 中 when().thenReturn(); 这种语法来定义对象方法和参数(输入),然后在 thenReturn 中指定结果(输出)。此过程称为 Stub 打桩 。一旦这个方法被 stub 了,就会一直返回这个 stub 的值。

  • 注意:

    1、对于 static 和 final 方法, Mockito 无法对其 when(…).thenReturn(…) 操作。当我们连续两次为同一个方法使用 stub 的时候,只会只用最新的一次。
    2、@Spy注解下,doReturn().when()是无副作用的,不会调用真实方法;when().thenReturn()是有副作用的,即会调用真实的方法,如果你不想调用真实的方法而是想要mock的话,建议使用doReturn().when()

verify

verify(class,time()).method();验证调用次数。

InOrder

调用顺序验证
InOrder inOrder=inOrder(mockClass);
inOrder.verify(mockClass).methd1();
inOrder.verify(mockClass).methd2();

断言

断言 (assertion) 是 org.junit.jupiter.api.Assertions 类上的众多静态方法之一。准备好测试实例、执行了被测类的方法以后,断言能确保你得到了想要的结果。一般的断言,无非是检查一个实例的属性(比如,判空与判非空等),或者对两个实例进行比较(比如,检查两个实例对象是否相等)等。无论哪种检查,断言方法都可以接受一个字符串作为最后一个可选参数,它会在断言失败时提供必要的描述信息。如果提供出错信息的过程比较复杂,它也可以被包装在一个 lambda 表达式中,这样,只有到真正失败的时候,消息才会真正被构造出来。
断言用于测试一个条件,该条件必须计算为 true ,测试才能继续执行。如果断言失败,测试会在断言所在的代码行上停止,并生成断言失败报告。如果断言成功,测试会继续执行下一行代码。
Assertions.assertEquals(expected,actual);
: 如果expected和actual不相等,断言失败;
Assertions.assertFalse(condition)
:如果condition不是false,断言失败;
Assertions.assertTrue(condition)
:如果condition不是true,断言失败;
Assertions.assertNull(actual)
:如果actual不是null,断言失败;
Assertions.assertNotNull(actual)
:如果actual是null,断言失败;

异常断言

JUnit5提供了一种新的断言方式Assertions.assertThrows(),配合函数式编程就可以进行使用。

示例代码

IntelliJ IDEA中用快捷键自动创建测试类的默认按键为:

ctrl+shift+t --> create new test–>勾选需要测试的方法
在这里插入图片描述

    @Test
    void deleteById_ShouldThrows2_WhenGivenStatusOnId() {
       ProgramEntity entity = new ProgramEntity();
        entity.setMiniProgramStatus(ProgramStatusEnum.ON.getValue());
        doReturn(entity).when(programMapper).selectById(1);
        Assertions.assertThrows(ReachBusinessException.class, () -> programService.deleteById(1),"小程序已上架不允许删除!");
    }

超时测试

当被测试的程序,逻辑非常复杂,不清楚是否存在问题,也要考虑这个程序的效率,在一定的时间内要执行完,这种情况要加一个timeout进行限制,当程序超过一定的时间没有执行完,就认为它是错误的。

参数化测试

    @ParameterizedTest
    @ValueSource(ints = {1, 2, 3})
    @DisplayName("程序上架成功")
    public void programUp_ShouldSuccess_WhenGivenRightParam(Integer programId) {
        doReturn(entity1).when(programMapper).selectById(miniProgramId);
        ProgramEntity updateEntity = new ProgramEntity();
        updateEntity.setMiniProgramStatus(ProgramStatusEnum.ON.getValue());
        updateEntity.setUpdateUser(OperationLogUtils.loginUserName());
        updateEntity.setId(entity1.getId());
        doReturn(1).when(ProgramMapper).updateById(updateEntity);
        programService.miniProgramUp(programId);
    }

内嵌单元测试

重复测试

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值