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

2次阅读

共计 5871 个字符,预计需要花费 15 分钟才能阅读完成。

欢送拜访我的 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

正文完
 0