JUnit 5 单元测试教程

点赞再看,动力无限。 微信搜「 程序猿阿朗 」。

本文 Github.com/niumoo/JavaNotes未读代码博客 已经收录,有很多知识点和系列文章。

在软件开发过程中,我们通常都需要测试自己的代码运行是否正常,可能对一个函数进行简单测试,也可能是多个功能的组合测试。不管使用哪种方式,都是为了更好的测试我们的代码是否存在逻辑缺陷。测试对于软件开发是非常必要的。

JUnit 5 介绍

在 Java 中比较有名的测试工具是 JUnit ,通常我们使用 JUnit 可以对一个逻辑单元进行测试,因此也叫单元测试。多个单元测试组合测试,可以确保我们的程序符合预期。JUnit 单元测试可以在开发阶段发现问题,让我们可以提前修复代码,因此十分重要。

JUnit 5 和 JUnit

JUnit 是一个 Java 语言的开源测试框架,使用 JUnit 让我们使用注解就可以进行单元测试,很是方便。

JUnit 5 是 JUnit 的升级版本,JUnit 5 使用了 Java 8 及更高版本的 Java 语言特性,如函数编程,流式编码等,因此更加强大。JUnit 5 进行单元测试的可读性更强,编写更加容易,且可以轻松扩展。

JUnit 5 基本组件

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

JUnit Platform

JUnit Platform 是 JUnit 的基础框架,使用 JUnit Platform 才能在 JVM 启动测试,JUnit Platform 还定义了 TestEngine 测试引擎,是JUnit 测试的基础。

JUnit Jupiter

JUnit Jupiter 提供了单元测试常见的注解以及扩展接口,想要方便的进行 JUnit 单元测试,那么 Jupiter 模块就必不可少。

JUnit Vintage

JUnit Vintage 提供了对 JUnit 3 和 JUnit 4 的测试支持。

JUnit 5 依赖

使用注解进行 JUnit 单元测试,直接引入 junit-jupiter即可。

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
  	<version>5.9.1</version>
    <scope>test</scope>
</dependency>

JUnit 5 常用注解

@Test

为一个 public void 方法添加 @Test 注释,允许我们对这个方法进行测试。

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/** 
 * @author:https://www.wdbyte.com  
 **/
class JUnitTestIsDog {

    @Test
    public void testIsDog() {
        String name = "cat";
        Assertions.assertEquals(name, "dog");
    }
}

上面的代码中使用了 Assertions.assertEquals(name, "dog") 来判断是否 name 变量是否是 dogAssertionsJUnit 提供的断言工具,后面会详细介绍。

idea 中运行可以到的错误日志,提示预期是 dog,实际是 cat

org.opentest4j.AssertionFailedError: 
Expected :cat
Actual   :dog
<Click to see difference>

如果是符合预期的,那么运行会显示正确标志。

@Test
public void testIsDog2() {
    String name = "dog";
    Assertions.assertEquals(name, "dog");
}

testIsDog2 方法测试通过。

@BeforeAll

使用 @BeforeAll 可以在单元测试前初始化部分信息,@BeforeAll 只能使用在静态方法上,被注解的方法会在测试开始前运行一次

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/** 
 * @author:https://www.wdbyte.com  
 **/
class JUnitBeforeAll {

    @BeforeAll
    public static void init() {
        System.out.println("初始化,准备测试信息");
    }

    @Test
    public void testIsDog() {
        String name = "dog";
        Assertions.assertEquals(name, "dog");
        System.out.println("is dog");
    }

    @Test
    public void testIsCat() {
        String name = "cat";
        Assertions.assertEquals(name, "cat");
        System.out.println("is cat");
    }
}

这会输出:

初始化,准备测试信息
is cat
is dog

@BeforeEach

使用 @BeforeEach 注解的方法,会在每一个 @Test 注解的方法运行前运行一次。

class JUnitBeforeAll {

    @BeforeAll
    public static void init() {
        System.out.println("初始化,准备测试信息");
    }

    @BeforeEach
    public void start(){
        System.out.println("开始测试...");
    }

    @Test
    public void testIsDog() {
        String name = "dog";
        Assertions.assertEquals(name, "dog");
        System.out.println("is dog");
    }

    @Test
    public void testIsCat() {
        String name = "cat";
        Assertions.assertEquals(name, "cat");
        System.out.println("is cat");
    }
}

这会输出:

初始化,准备测试信息
开始测试...
is cat
开始测试...
is dog

@AfterAll

@AfterAll 注解只能使用在静态方法上,被注解的方法会在所有单元测试运行完毕后运行一次。

class JUnitBeforeAll {

    @BeforeAll
    public static void init() {
        System.out.println("初始化,准备测试信息");
    }

    @BeforeEach
    public void start(){
        System.out.println("开始测试...");
    }

    @Test
    public void testIsDog() {
       //...
    }

    @Test
    public void testIsCat() {
        //...
    }

    @AfterAll
    public static void close() {
        System.out.println("结束,准备退出测试");
    }
}

这会输出:

初始化,准备测试信息
开始测试...
is cat
开始测试...
is dog
结束,准备退出测试

@AfterEach

使用 @AfterEach 注解的方法,会在每一个 @Test 注解的方法运行结束前运行一次

class JUnitBeforeAll {

    @BeforeAll
    public static void init() {
        System.out.println("初始化,准备测试信息");
    }

    @BeforeEach
    public void start(){
        System.out.println("开始测试...");
    }

    @Test
    public void testIsDog() { //... }

    @Test
    public void testIsCat() { //... }

    @AfterEach
    public void end(){
        System.out.println("测试完毕...");
    }

    @AfterAll
    public static void close() {
        System.out.println("结束,准备退出测试");
    }
}

这会输出:

初始化,准备测试信息
开始测试...
is cat
测试完毕...
开始测试...
is dog
测试完毕...
结束,准备退出测试

@Disabled

@Disabled 注解的方法不在参与测试,下面对 testIsDog 方法添加了 @Disabled 注解。

class JUnitBeforeAll {

    @BeforeAll
    public static void init() {
        System.out.println("初始化,准备测试信息");
    }

    @BeforeEach
    public void start(){
        System.out.println("开始测试...");
    }

    @Disabled("由于xx原因,关闭 testIsDog 测试")
    @Test
    public void testIsDog() {
        String name = "dog";
        Assertions.assertEquals(name, "dog");
        System.out.println("is dog");
    }

    @Test
    public void testIsCat() {
        String name = "cat";
        Assertions.assertEquals(name, "cat");
        System.out.println("is cat");
    }

    @AfterEach
    public void end(){
        System.out.println("测试完毕...");
    }

    @AfterAll
    public static void close() {
        System.out.println("结束,准备退出测试");
    }
}

这会输出:

初始化,准备测试信息
开始测试...
is cat
测试完毕...

由于xx原因,关闭 testIsDog 测试
结束,准备退出测试

@DisplayName

使用 @DisplayName 注解可以自定义测试方法的显示名称,下面为两个测试方法自定义名称。

class JUnitBeforeAll {

    @BeforeAll
    public static void init() {
        System.out.println("初始化,准备测试信息");
    }

    @BeforeEach
    public void start() {
        System.out.println("开始测试...");
    }

    @DisplayName("是否是狗")
    @Disabled
    @Test
    public void testIsDog() {
        String name = "dog";
        Assertions.assertEquals(name, "dog");
        System.out.println("is dog");
    }

    @DisplayName("是否是猫")
    @Test
    public void testIsCat() {
        String name = "cat";
        Assertions.assertEquals(name, "cat");
        System.out.println("is cat");
    }

    @AfterEach
    public void end() {
        System.out.println("测试完毕...");
    }

    @AfterAll
    public static void close() {
        System.out.println("结束,准备退出测试");
    }
}

idea 中运行后,可以看到配置的中文名称。

@ParameterizedTest

使用注解 @ParameterizedTest 结合 @ValueSource ,可以对不用的入参进行测试。下面的示例使用 @ParameterizedTest 来开始参数化单元测试,name 属性用来定义测试名称, @ValueSource 则定义了两个测试值。

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

public class JUnitParam {

    //@Test
    @DisplayName("是否是狗")
    @ValueSource(strings = {"dog", "cat"})
    @ParameterizedTest(name = "开始测试入参 {0} ")
    public void testIsDog(String name) {
        Assertions.assertEquals(name, "dog");
    }
}

这会输出:

@Order

在类上增加注解 @TestMethodOrder ,然后在方法上使用 @Order 指定顺序,数字越小优先级越搞,可以保证测试方法运行顺序。

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.condition.EnabledOnJre;

import static org.junit.jupiter.api.condition.JRE.JAVA_19;

@TestMethodOrder(OrderAnnotation.class)
public class JUnitOrder{

    @Test
    @DisplayName("测试是否是狗")
    @Order(2)
    public void testIsDog() {
        String name = "dog";
        Assertions.assertEquals(name, "dog");
        System.out.println("is dog");
    }

    @DisplayName("是否是猫")
    @Test
    @Order(1)
    public void testIsCat() {
        String name = "cat";
        Assertions.assertEquals(name, "cat");
        System.out.println("is cat");
    }
}

这会输出:

is cat
is dog

其他注解

@EnabledOnJre(JAVA_19)

只在 JRE 19 环境运行,否则运行会输出:Disabled on JRE version: xxx.

@RepeatedTest(10)

重复测试,参数 10 可以让单元测试重复运行 10 次。

JUnit 5 常用断言

在上面的例子中,已经用到了 assertEquals 来判断结果是否符合预期,assertEquals是类 org.junit.jupiter.api.Assertions 中的一个方法;除此之外,还几乎包括了所有我们日常测试想要用到的判断方法。

下面是一些演示:

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class JunitAssert {

    @DisplayName("是否是狗")
    @Test
    public void testIsDog() {
        String name = "dog";
        Assertions.assertNotNull(name);
        Assertions.assertEquals(name, "dog");
        Assertions.assertNotEquals(name, "cat");
        Assertions.assertTrue("dog".equals(name));
        Assertions.assertFalse("cat".equals(name));
    }

    @DisplayName("是否是猫")
    @Test
    public void testIsCat() {
        String name = "cat";
        Assertions.assertNull(name, "name is not null");
    }

}

testIsDog 中演示了一些常用的判断方法,且都可以通过验证。在 testIsCat 方法中进行了 null 值判断,显然这里无法通过测试,会抛出自定义异常 name is not null

这会输出:

org.opentest4j.AssertionFailedError: name is not null ==> 
Expected :null
Actual   :cat
<Click to see difference>

预期是一个 null 值,实际上是一个 cat 字符串。

Maven JUnit 测试

在 Maven 中进行 JUnit 测试,可以通过命令 mvn test 开始测试,默认情况下会测试所有依赖了当前源码的 JUnit 测试用例。

准备被测 Preson类放在 src.main.java.com.wdbyte.test.junit5.

package com.wdbyte.test.junit5;

public class Person {
    public int getLuckyNumber() {
        return 7;
    }
}

编写测试类 PersonTest 放在 src.test.java.com.wdbyte.test.junit5. 这里判断获取到的幸运数字是否是 8 ,明显方法返回的是 7 ,所以这里是测试会报错。

package com.wdbyte.test.junit5;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

@DisplayName("测试 Presion")
class PersonTest {

    @DisplayName("测试幸运数字")
    @Test
    void getLuckyNumber() {
        Person person = new Person();
        Assertions.assertEquals(8, person.getLuckyNumber());
    }
}

在 pom.xml 中引入 maven junit 测试依赖插件。

<build>
     <plugins>
         <plugin>
             <artifactId>maven-surefire-plugin</artifactId>
             <version>2.22.2</version>
         </plugin>
         <plugin>
             <artifactId>maven-failsafe-plugin</artifactId>
             <version>2.22.2</version>
         </plugin>
     </plugins>
</build>

执行测试命令:mvn test

➜  junit5-jupiter-starter git:(master) ✗ mvn test
[INFO] Scanning for projects...
[INFO] ....
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.wdbyte.test.junit5.PersonTest
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.031 s <<< FAILURE! - in com.wdbyte.test.junit5.PersonTest
[ERROR] getLuckyNumber  Time elapsed: 0.026 s  <<< FAILURE!
org.opentest4j.AssertionFailedError: expected: <8> but was: <7>
	at com.wdbyte.test.junit5.PersonTest.getLuckyNumber(PersonTest.java:18)

[INFO]
[INFO] Results:
[INFO]
[ERROR] Failures:
[ERROR]   PersonTest.getLuckyNumber:18 expected: <8> but was: <7>
[INFO]
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.777 s
[INFO] Finished at: 2022-11-17T23:01:09+08:00
[INFO] ------------------------------------------------------------------------

也可以指定类进行测试:mvn -Dtest=PersonTest test

一如既往,文章中代码存放在 Github.com/niumoo/javaNotes.

<完>

文章持续更新,可以微信搜一搜「 程序猿阿朗 」或访问「程序猿阿朗博客 」第一时间阅读。本文 Github.com/niumoo/JavaNotes 已经收录,有很多知识点和系列文章,欢迎Star。

原文地址:https://www.cnblogs.com/niumoo/p/16902100.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 目录 连接 连接池产生原因 连接池实现原理 小结 TEMPERANCE:Eat not to dullness;drink not to elevation.节制
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 一个优秀的工程师和一个普通的工程师的区别,不是满天飞的架构图,他的功底体现在所写的每一行代码上。-- 毕玄 1. 命名风格 【书摘】类名用 UpperCamelC
今天犯了个错:“接口变动,伤筋动骨,除非你确定只有你一个人在用”。哪怕只是throw了一个新的Exception。哈哈,这是我犯的错误。一、接口和抽象类类,即一个对象。先抽象类,就是抽象出类的基础部分,即抽象基类(抽象类)。官方定义让人费解,但是记忆方法是也不错的 —包含抽象方法的类叫做抽象类。接口
Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:BYSocketFaceBook:BYSocketTwitter :BYSocket一、引子文件,作为常见的数据源。关于操作文件的字节流就是 —FileInputStream&amp;FileOutputStream。
作者:泥沙砖瓦浆木匠网站:http://blog.csdn.net/jeffli1993个人签名:打算起手不凡写出鸿篇巨作的人,往往坚持不了完成第一章节。交流QQ群:【编程之美 365234583】http://qm.qq.com/cgi-bin/qm/qr?k=FhFAoaWwjP29_Aonqz
本文目录 线程与多线程 线程的运行与创建 线程的状态 1 线程与多线程 线程是什么? 线程(Thread)是一个对象(Object)。用来干什么?Java 线程(也称 JVM 线程)是 Java 进程内允许多个同时进行的任务。该进程内并发的任务成为线程(Thread),一个进程里至少一个线程。 Ja
Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:BYSocketFaceBook:BYSocketTwitter :BYSocket在面向对象编程中,编程人员应该在意“资源”。比如?1String hello = &quot;hello&quot;; 在代码中,我们
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 这是泥瓦匠的第103篇原创 《程序兵法:Java String 源码的排序算法(一)》 文章工程:* JDK 1.8* 工程名:algorithm-core-le
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 目录 一、父子类变量名相同会咋样? 有个小故事,今天群里面有个人问下面如图输出什么? 我回答:60。但这是错的,答案结果是 40 。我知错能改,然后说了下父子类变
作者:泥瓦匠 出处:https://www.bysocket.com/2021-10-26/mac-create-files-from-the-root-directory.html Mac 操作系统挺适合开发者进行写代码,最近碰到了一个问题,问题是如何在 macOS 根目录创建文件夹。不同的 ma
作者:李强强上一篇,泥瓦匠基础地讲了下Java I/O : Bit Operation 位运算。这一讲,泥瓦匠带你走进Java中的进制详解。一、引子在Java世界里,99%的工作都是处理这高层。那么二进制,字节码这些会在哪里用到呢?自问自答:在跨平台的时候,就凸显神功了。比如说文件读写,数据通信,还
1 线程中断 1.1 什么是线程中断? 线程中断是线程的标志位属性。而不是真正终止线程,和线程的状态无关。线程中断过程表示一个运行中的线程,通过其他线程调用了该线程的 方法,使得该线程中断标志位属性改变。 深入思考下,线程中断不是去中断了线程,恰恰是用来通知该线程应该被中断了。具体是一个标志位属性,
Writer:BYSocket(泥沙砖瓦浆木匠)微博:BYSocket豆瓣:BYSocketReprint it anywhere u want需求 项目在设计表的时候,要处理并发多的一些数据,类似订单号不能重复,要保持唯一。原本以为来个时间戳,精确到毫秒应该不错了。后来觉得是错了,测试环境下很多一
纯技术交流群 每日推荐 - 技术干货推送 跟着泥瓦匠,一起问答交流 扫一扫,我邀请你入群 纯技术交流群 每日推荐 - 技术干货推送 跟着泥瓦匠,一起问答交流 扫一扫,我邀请你入群 加微信:bysocket01
Writer:BYSocket(泥沙砖瓦浆木匠)微博:BYSocket豆瓣:BYSocketReprint it anywhere u want.文章Points:1、介绍RESTful架构风格2、Spring配置CXF3、三层初设计,实现WebService接口层4、撰写HTTPClient 客户
Writer :BYSocket(泥沙砖瓦浆木匠)什么是回调?今天傻傻地截了张图问了下,然后被陈大牛回答道“就一个回调…”。此时千万个草泥马飞奔而过(逃哈哈,看着源码,享受着这种回调在代码上的作用,真是美哉。不妨总结总结。一、什么是回调回调,回调。要先有调用,才有调用者和被调用者之间的回调。所以在百
Writer :BYSocket(泥沙砖瓦浆木匠)一、什么大小端?大小端在计算机业界,Endian表示数据在存储器中的存放顺序。百度百科如下叙述之:大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加
What is a programming language? Before introducing compilation and decompilation, let&#39;s briefly introduce the Programming Language. Programming la
Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:BYSocketFaceBook:BYSocketTwitter :BYSocket泥瓦匠喜欢Java,文章总是扯扯Java。 I/O 基础,就是二进制,也就是Bit。一、Bit与二进制什么是Bit(位)呢?位是CPU
Writer:BYSocket(泥沙砖瓦浆木匠)微博:BYSocket豆瓣:BYSocket一、前言 泥瓦匠最近被项目搞的天昏地暗。发现有些要给自己一些目标,关于技术的目标:专注很重要。专注Java 基础 + H5(学习) 其他操作系统,算法,数据结构当成课外书博览。有时候,就是那样你越是专注方面越