关于云计算:JUnit5学习之三Assertions类

欢送拜访我的GitHub

https://github.com/zq2599/blog_demos

内容:所有原创文章分类汇总及配套源码,波及Java、Docker、Kubernetes、DevOPS等;

对于《JUnit5学习》系列

《JUnit5学习》系列旨在通过实战晋升SpringBoot环境下的单元测试技能,一共八篇文章,链接如下:

  1. 基本操作
  2. Assumptions类
  3. Assertions类
  4. 按条件执行
  5. 标签(Tag)和自定义注解
  6. 参数化测试(Parameterized Tests)根底
  7. 参数化测试(Parameterized Tests)进阶
  8. 综合进阶(终篇)

本篇概览

本文是《JUnit5学习》系列的第三篇,次要是学习Assertions类(org.junit.jupiter.api.Assertions),Assertions类的一系列静态方法给咱们提供了单元测试时罕用的断言性能,本篇次要内容如下:

  1. Assertions源码剖析
  2. 写一段代码,应用Assertions的罕用静态方法
  3. 应用异样断言
  4. 应用超时断言
  5. 理解第三方断言库

源码下载

  1. 如果您不想编码,能够在GitHub下载所有源码,地址和链接信息如下表所示:
名称 链接 备注
我的项目主页 https://github.com/zq2599/blo… 该我的项目在GitHub上的主页
git仓库地址(https) https://github.com/zq2599/blo… 该我的项目源码的仓库地址,https协定
git仓库地址(ssh) git@github.com:zq2599/blog_demos.git 该我的项目源码的仓库地址,ssh协定
  1. 这个git我的项目中有多个文件夹,本章的利用在<font color=”blue”>junitpractice</font>文件夹下,如下图红框所示:

  1. <font color=”blue”>junitpractice</font>是父子构造的工程,本篇的代码在<font color=”red”>assertassume</font>子工程中,如下图:

Assertions源码剖析

  1. 下图是一段最简略最常见的单元测试代码,也就是Assertions.assertEquals办法,及其执行成果:

  1. 将Assertions.assertEquals办法逐层开展,如下图所示,可见入参expected和actual的值如果不相等,就会在AssertionUtils.fail办法中抛出AssertionFailedError异样:

  1. 用类图工具查看<font color=”blue”>Assertions</font>类的办法,如下图,大部分是与assertEquals办法相似的判断,例如对象是否为空,数组是否相等,判断失败都会抛出AssertionFailedError异样:

  1. 判断两个数组是否相等的逻辑与判断两个对象略有不同,能够重点看看,办法源码如下:
    public static void assertArrayEquals(Object[] expected, Object[] actual) {
        AssertArrayEquals.assertArrayEquals(expected, actual);
    }
  1. 将上述代码逐层开展,在AssertArrayEquals.java中见到了残缺的数组比拟逻辑,如下图:

  • 接下来,咱们编写一些单元测试代码,把Assertions类罕用的办法都相熟一遍;

编码实战

  1. 关上junitpractice工程的子工程assertassume,新建测试类<font color=”blue”>AssertionsTest.java</font>:

  1. 最简略的判断,两个入参相等就不抛异样(AssertionFailedError):
    @Test
    @DisplayName("最一般的判断")
    void standardTest() {
       assertEquals(2, Math.addExact(1, 1));
    }
  1. 还有另一个assertEquals办法,能承受<font color=”blue”>Supplier</font>类型的入参,当判断不通过时才会调用Supplier.get办法获取字符串作为失败提醒音讯(如果测试通过则Supplier.get办法不会被执行):
    @Test
    @DisplayName("带失败提醒的判断(拼接音讯字符串的代码只有判断失败时才执行)")
    void assertWithLazilyRetrievedMessage() {
        int expected = 2;
        int actual = 1;

        assertEquals(expected,
                actual,
                // 这个lambda表达式,只有在expected和actual不相等时才执行
                ()->String.format("期望值[%d],理论值[%d]", expected, actual));
    }
  1. assertAll办法能够将多个判断逻辑放在一起解决,只有有一个报错就会导致整体测试不通过,并且执行后果中会给出具体的失败详情:
    @Test
    @DisplayName("批量判断(必须全副通过,否则就算失败)")
    void groupedAssertions() {
        // 将多个判断放在一起执行,只有全副通过才算通过,如果有未通过的,会有对应的提醒
        assertAll("单个测试方法中多个判断",
                () -> assertEquals(1, 1),
                () -> assertEquals(2, 1),
                () -> assertEquals(3, 1)
        );
    }

上述代码执行后果如下:

异样断言

  1. Assertions.assertThrows办法,用来测试Executable实例执行execute办法时是否抛出指定类型的异样;
  2. 如果execute办法执行时不抛出异样,或者抛出的异样与冀望类型不统一,都会导致测试失败;
  3. 写段代码验证一下,如下,1除以0会抛出<font color=”blue”>ArithmeticException</font>异样,合乎assertThrows指定的异样类型,因而测试能够通过:
    @Test
    @DisplayName("判断抛出的异样是否是指定类型")
    void exceptionTesting() {

        // assertThrows的第二个参数是Executable,
        // 其execute办法执行时,如果抛出了异样,并且异样的类型是assertThrows的第一个参数(这里是ArithmeticException.class),
        // 那么测试就通过了,返回值是异样的实例
        Exception exception = assertThrows(ArithmeticException.class, () -> Math.floorDiv(1,0));

        log.info("assertThrows通过后,返回的异样实例:{}", exception.getMessage());
    }
  • 以上是Assertions的惯例用法,接下来要重点关注的就是和超时相干的测试方法;

超时相干的测试

  1. 超时测试的次要指标是验证指定代码是否在规定工夫内执行完,最罕用的<font color=”blue”>assertTimeout</font>办法外部实现如下图,可见被测试的代码通过<font color=”blue”>ThrowingSupplier</font>实例传入,被执行后再查看耗时是否超过规定工夫,超过就调用fail办法抛AssertionFailedError异样:

  1. assertTimeout的用法如下,冀望工夫是1秒,实际上Executable实例的execute用了两秒才实现,因而测试失败:
    @Test
    @DisplayName("在指定工夫内实现测试")
    void timeoutExceeded() {
        // 指定工夫是1秒,理论执行用了2秒
        assertTimeout(ofSeconds(1), () -> {
            try{
              Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }

执行后果如下图:

  1. 下面的演示中,assertTimeout的第二个入参类型是<font color=”blue”>Executable</font>,此外还有另一个assertTimeout办法,其第二个入参是<font color=”blue”>ThrowingSupplier</font>类型,该类型入参的get办法必须要有返回值,假如是XXX,而assertTimeout就拿这个XXX作为它本人的返回值,应用办法如下:
    @Test
    @DisplayName("在指定工夫内实现测试")
    void timeoutNotExceededWithResult() {

        // 筹备ThrowingSupplier类型的实例,
        // 外面的get办法sleep了1秒钟,而后返回一个字符串
        ThrowingSupplier<String> supplier = () -> {

            try{
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            return "我是ThrowingSupplier的get办法的返回值";
        };

        // 指定工夫是2秒,实际上ThrowingSupplier的get办法只用了1秒
        String actualResult = assertTimeout(ofSeconds(2), supplier);

        log.info("assertTimeout的返回值:{}", actualResult);
    }

上述代码执行后果如下,测试通过并且ThrowingSupplier实例的get办法的返回值也被打印进去:

  1. 方才咱们看过了assertTimeout的外部实现代码,是将入参Executable的execute办法执行实现后,再查看execute办法的耗时是否超过预期,这种办法的弊病是必须期待execute办法执行实现才晓得是否超时,assertTimeoutPreemptively办法也是用来检测代码执行是否超时的,然而防止了assertTimeout的必须期待execute执行实现的弊病,防止的办法是用一个新的线程来执行execute办法,上面是assertTimeoutPreemptively的源码:
public static void assertTimeoutPreemptively(Duration timeout, Executable executable) {
    AssertTimeout.assertTimeoutPreemptively(timeout, executable);
}
  1. assertTimeoutPreemptively办法的Executable入参,其execute办法会在一个新的线程执行,假如是XXX线程,当等待时间超过入参timeout的值时,XXX线程就会被中断,并且测试后果是失败,上面是assertTimeoutPreemptively的用法演示,设置的超时工夫是2秒,而Executable实例的execute却sleep了10秒:
    @Test
    void timeoutExceededWithPreemptiveTermination() {
        log.info("开始timeoutExceededWithPreemptiveTermination");
        assertTimeoutPreemptively(ofSeconds(2), () -> {
            log.info("开始sleep");
            try{
                Thread.sleep(10000);
                log.info("sleep了10秒");
            } catch (InterruptedException e) {
                log.error("线程sleep被中断了", e);
            }
        });
    }
  1. 来看看执行后果,如下图,通过日志可见,Executable的execute办法是在新的线程执行的,并且被中断了,提前完成单元测试,测试后果是不通过:

第三方断言库

  1. 除了junit的Assertions类,还能够抉择第三方库提供的断言能力,比拟典型的有AssertJ, Hamcrest, Truth这三种,它们都有各自的特色和实用场景,例如Hamcrest的特点是匹配器(matchers ),而Truth来自谷歌的Guava团队,编写的代码是链式调用格调,简略易读,断言类型绝对更少却不失性能;
  2. springboot默认依赖了hamcrest库,依赖关系如下图:

  1. 一个简略的基于hamcrest的匹配器的单元测试代码如下,因为预期和理论的值不相等,因而会匹配失败:
package com.bolingcavalry.assertassume.service.impl;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

@SpringBootTest
@Slf4j
public class HamcrestTest {

    @Test
    @DisplayName("体验hamcrest")
    void assertWithHamcrestMatcher() {
        assertThat(Math.addExact(1, 2), is(equalTo(5)));
    }
}
  1. 执行后果如下:

  • 以上就是JUnit5罕用的断言性能,心愿本篇能助您夯实根底,为后续写出更适合的用例做好筹备;

你不孤独,欣宸原创一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 数据库+中间件系列
  6. DevOps系列

欢送关注公众号:程序员欣宸

微信搜寻「程序员欣宸」,我是欣宸,期待与您一起畅游Java世界…
https://github.com/zq2599/blog_demos

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理