五年了,你还在用Junit4吗?
junit5
JUnit5在2017年就发布了,你还在用junit4吗?
什么是junit5
与以前的JUnit版本不同,JUnit 5由三个不同子项目的多个不同模块组成。
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
JUnit Platform为在JVM上启动测试框架提供基础。它还定义了TestEngine API, 用来开发在平台上运行的测试框架。此外,平台提供了一个控制台启动器],用于从命令行启动平台,并为Gradle和Maven提供构建插件以[基于JUnit 4的Runner,用于在平台上运行任意TestEngine
。
JUnit Jupiter是在JUnit 5中编写测试和扩展的新型编程模型和[扩展模型][]的组合.Jupiter子项目提供了TestEngine
,用于在平台上运行基于Jupiter的测试。
JUnit Vintage提供TestEngine
,用于在平台上运行基于JUnit 3和JUnit 4的测试。
为什么需要 JUnit 5
自从有了类似 JUnit 之类的测试框架,Java 单元测试领域逐渐成熟,开发人员对单元测试框架也有了更高的要求:更多的测试方式,更少的其他库的依赖。
因此,大家期待着一个更强大的测试框架诞生,JUnit 作为Java测试领域的领头羊,推出了 JUnit 5 这个版本,主要特性:
- 提供全新的断言和测试注解,支持测试类内嵌
- 更丰富的测试方式:支持动态测试,重复测试,参数化测试等
- 实现了模块化,让测试执行和测试发现等不同模块解耦,减少依赖
- 提供对 Java 8 的支持,如 Lambda 表达式,Sream API等。
基本注解
@Test: 表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
@ParameterizedTest: 表示方法是参数化测试
@RepeatedTest: 表示方法可重复执行
@DisplayName: 为测试类或者测试方法设置展示名称
@BeforeEach: 表示在每个单元测试之前执行
@AfterEach: 表示在每个单元测试之后执行
@BeforeAll: 表示在所有单元测试之前执行
@AfterAll: 表示在所有单元测试之后执行
@Tag: 表示单元测试类别,类似于JUnit4中的@Categories
@Disabled: 表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
@Timeout: 表示测试方法运行如果超过了指定时间将会返回错误
@ExtendWith: 为测试类或测试方法提供扩展类引用
常用注解格式:
class StandardTests {
//与junit4的@beforeClass类似,每个测试类运行一次
@BeforeAll
static void initAll() {
}
//与junit4中@before类似,每个测试用例都运行一次
@BeforeEach
void init() {
}
@Test
@DisplayName("成功测试")
void succeedingTest() {
}
@Test
@DisplayName("失败测试")
void failingTest() {
fail("a failing test");
}
//禁用测试用例
@Test
@Disabled("for demonstration purposes")
void skippedTest() {
// not executed
}
@Test
void abortedTest() {
assumeTrue("abc".contains("Z"));
fail("test should have been aborted");
}
//与@BeforeEach对应,每个测试类执行一次,一般用于恢复环境
@AfterEach
void tearDown() {
}
//与@BeforeAll对应,每个测试类执行一次,一般用于恢复环境
@AfterAll
static void tearDownAll() {
}
}
新特性
显示名称
@DisplayName("显示名称测试")
class DisplayNameDemo {
@Test
@DisplayName("我的 第一个 测试 用例")
void testWithDisplayNameContainingSpaces() {
}
@Test
@DisplayName("╯°□°)╯")
void testWithDisplayNameContainingSpecialCharacters() {
}
@Test
@DisplayName("")
void testWithDisplayNameContainingEmoji() {
}
}
IDE运行测试结果显示:
优点:通过这种方式,可以在方法名是英文特别长或者很难用英文描述清楚的场景下,增加中文解释
更强大的断言
JUnit Jupiter提供了许多JUnit4已有的断言方法,并增加了一些适合与Java 8 lambda一起使用的断言方法。所有JUnit Jupiter断言都是[org.junit.jupiter.Assertions]类中的静态方法。
分组断言:
多个条件同时满足时才断言成功
@Test
void groupedAssertions() {
Person person = new Person();
Assertions.assertAll("person",
() -> assertEquals("niu", person.getName()),
() -> assertEquals(18, person.getAge())
);
}
异常断言:
Junit4时需要使用rule方式,junit5提供了assertThrows更优雅的异常断言
@Test
void exceptionTesting() {
Throwable exception = assertThrows(IllegalArgumentException.class, () -> {
throw new IllegalArgumentException("a message");
});
assertEquals("a message", exception.getMessage());
}
超时断言:
@Test
@DisplayName("超时测试")
public void timeoutTest() {
Assertions.assertTimeout(Duration.ofMillis(100), () -> Thread.sleep(50));
}
标签和过滤
通过标签把测试分组,在不同阶段执行不同的逻辑测试,比如划分为快速冒烟测试和执行慢但也重要的测试
@Test
@Tag("fast")
void testing_faster() {
}
@Test
@Tag("slow")
void testing_slow() {
}
然后通过配置maven-surefire-plugin插件
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.0</version>
<configuration>
<properties>
<includeTags>fast</includeTags>
<excludeTages>slow</excludeTages>
</properties>
</configuration>
</plugin>
嵌套测试
当我们编写的类和代码逐渐增多,随之而来的需要测试的对应测试类也会越来越多。
为了解决测试类数量爆炸的问题,JUnit 5提供了@Nested 注解,能够以静态内部成员类的形式对测试用例类进行逻辑分组。
并且每个静态内部类都可以有自己的生命周期方法, 这些方法将按从外到内层次顺序执行。
此外,嵌套的类也可以用@DisplayName 标记,这样我们就可以使用正确的测试名称。下面看下简单的用法:
@DisplayName("A stack")
class TestingAStackDemo {
Stack<Object> stack;
@Test
@DisplayName("is instantiated with new Stack()")
void isInstantiatedWithNew() {
new Stack<>();
}
@Nested
@DisplayName("when new")
class WhenNew {
@BeforeEach
void createNewStack() {
stack = new Stack<>();
}
@Test
@DisplayName("is empty")
void isEmpty() {
assertTrue(stack.isEmpty());
}
@Nested
@DisplayName("after pushing an element")
class AfterPushing {
String anElement = "an element";
@BeforeEach
void pushAnElement() {
stack.push(anElement);
}
@Test
@DisplayName("it is no longer empty")
void isNotEmpty() {
assertFalse(stack.isEmpty());
}
}
}
}
junit没有限制嵌套层数,除非必要一般不建议使用超过3层,过于复杂的层次结构会增加开发者理解用例关系的难度
构造函数和方法的依赖注入
在之前的所有JUnit版本中,测试构造函数或方法都不允许有参数(至少不能使用标准的Runner实现)。作为JUnit Jupiter的主要变化之一,测试构造函数和方法现在都允许有参数。这带来了更大的灵活性,并为构造函数和方法启用依赖注入
- TestInfo可获取测试信息
- TestReporter可以向控制台输出信息
@Test
@DisplayName("test-first")
@Tag("my-tag")
void test1(TestInfo testInfo) {
assertEquals("test-first", testInfo.getDisplayName());
assertTrue(testInfo.getTags().contains("my-tag"));
}
@Test
@DisplayName("test-second")
@Tag("my-tag")
void test2(TestReporter testReporter) {
testReporter.publishEntry("a key", "a value");
}
重复测试
多次调用同一个测试用例
@RepeatedTest(10)
@DisplayName("重复测试")
public void testRepeated() {
//...
}
动态测试
动态测试只需要编写一处代码,就能一次性对各种类型的输入和输出结果进行验证
@TestFactory
@DisplayName("动态测试")
Stream<DynamicTest> dynamicTests() {
List<Person> persons = getAllPerson();
return persons.stream()
.map(person -> DynamicTest.dynamicTest(person.getName() + "-test", () -> assertTrue(person.getName().contains("niu"))));
}
超时测试
通过时间来验证用例是否超时,一般要求单个单元测试不应该超过1秒
class TimeoutDemo {
@BeforeEach
@Timeout(5)
void setUp() {
// fails if execution time exceeds 5 seconds
}
@Test
@Timeout(value = 1000, unit = TimeUnit.MILLISECONDS)
void failsIfExecutionTimeExceeds1000Milliseconds() {
// fails if execution time exceeds 1000 milliseconds
//也可用这种方式 Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(1500));
}
}
参数测试
参数测试我觉得是最好用的特性,可以大量减少重复模板式代码,也是junit5最惊艳的提升,强烈推荐使用
@ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
@NullSource: 表示为参数化测试提供一个null的入参
@EnumSource: 表示为参数化测试提供一个枚举入参
@CsvSource:表示读取CSV格式内容作为参数化测试入参
@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
@ArgumentsSource:指定一个自定义的,可重用的
ArgumentsProvider
。
看完用法描述,简直太喜欢了
一个顶三个基础测试用例
@ParameterizedTest
@ValueSource(strings = {"one", "two", "three"})
@DisplayName("参数化测试1")
public void parameterizedTest1(String string) {
assertTrue(StringUtils.isNotBlank(string));
}
如果不是基础的类型,可以使用方法构造,只要返回值为Stream类型就可以,多个参数使用Arguments
实例流
@ParameterizedTest
@MethodSource("method")
@DisplayName("方法来源参数")
public void testWithExplicitLocalMethodSource(String name) {
Assertions.assertNotNull(name);
}
private static Stream<String> method() {
return Stream.of("apple", "banana");
}
@CsvSource
允许您将参数列表表示为以逗号分隔的值(例如,字符串文字)
@ParameterizedTest
@CsvSource({"steven,18", "jack,24"})
@DisplayName("参数化测试-csv格式")
public void parameterizedTest3(String name, Integer age) {
System.out.println("name:" + name + ",age:" + age);
Assertions.assertNotNull(name);
Assertions.assertTrue(age > 0);
}
@CsvFileSource
使用classpath中的CSV文件,CSV文件中的每一行都会导致参数化测试的一次调用
这种就完全把测试数据与测试方法隔离,达到更好解耦效果
@ParameterizedTest
@CsvFileSource(resources = "/persons.csv") //指定csv文件位置
@DisplayName("参数化测试-csv文件")
public void parameterizedTest2(String name, Integer age) {
System.out.println("name:" + name + ",age:" + age);
Assertions.assertNotNull(name);
Assertions.assertTrue(age > 0);
}
其他方式不在赘述,如果还是满足不了需求,可以通过@ArgumentsSource自定义自己的数据来源,必须封装成去取JSON或者XMl等数据
AssertJ
当定义好需要运行的测试方法后,下一步则是需要关注测试方法的细节,这就离不开断言和假设
断言:封装好了常用判断逻辑,当不满足条件时,该测试用例会被认为测试失败
假设:与断言类似,当条件不满足时,测试会直接退出而不是判定为失败
因为不会影响到后续的测试用例,最常用的还是断言
除了Junit5自带的断言,AssertJ是非常好用的一个断言工具,最大特点是提供了流式断言,与Java8使用方法非常类似
@Test
void testString() {
// 断言null或为空字符串
assertThat("").isNullOrEmpty();
// 断言空字符串
assertThat("").isEmpty();
// 断言字符串相等 断言忽略大小写判断字符串相等
assertThat("niu").isEqualTo("niu").isEqualToIgnoringCase("NIu");
// 断言开始字符串 结束字符穿 字符串长度
assertThat("niu").startsWith("ni").endsWith("u").hasSize(3);
// 断言包含字符串 不包含字符串
assertThat("niu").contains("iu").doesNotContain("love");
// 断言字符串只出现过一次
assertThat("niu").containsOnlyOnce("iu");
}
@Test
void testNumber() {
// 断言相等
assertThat(42).isEqualTo(42);
// 断言大于 大于等于
assertThat(42).isGreaterThan(38).isGreaterThanOrEqualTo(38);
// 断言小于 小于等于
assertThat(42).isLessThan(58).isLessThanOrEqualTo(58);
// 断言0
assertThat(0).isZero();
// 断言正数 非负数
assertThat(1).isPositive().isNotNegative();
// 断言负数 非正数
assertThat(-1).isNegative().isNotPositive();
}
@Test
void testCollection() {
// 断言 列表是空的
assertThat(newArrayList()).isEmpty();
// 断言 列表的开始 结束元素
assertThat(newArrayList(1, 2, 3)).startsWith(1).endsWith(3);
// 断言 列表包含元素 并且是排序的
assertThat(newArrayList(1, 2, 3)).contains(1, atIndex(0)).contains(2, atIndex(1)).contains(3)
.isSorted();
// 断言 被包含与给定列表
assertThat(newArrayList(3, 1, 2)).isSubsetOf(newArrayList(1, 2, 3, 4));
// 断言 存在唯一元素
assertThat(newArrayList("a", "b", "c")).containsOnlyOnce("a");
}
@Test
void testMap() {
Map<String, Object> foo = ImmutableMap.of("A", 1, "B", 2, "C", 3);
// 断言 map 不为空 size
assertThat(foo).isNotEmpty().hasSize(3);
// 断言 map 包含元素
assertThat(foo).contains(entry("A", 1), entry("B", 2));
// 断言 map 包含key
assertThat(foo).containsKeys("A", "B", "C");
// 断言 map 包含value
assertThat(foo).containsValue(3);
}
// 其他断言,请自行探索......
想想如果没有使用AssertJ时我们是如何写断言的,是不是需要多个assert,很繁琐
AssertJ的断言代码清爽很多,流式断言充分利用了java8之后的匿名方法和stream类型的特点,很好的对Junit断言方法做了补充。
参考
五年了,你还在用Junit4吗?的更多相关文章
- JVM内存管理------GC算法精解(五分钟让你彻底明白标记/清除算法)
相信不少猿友看到标题就认为LZ是标题党了,不过既然您已经被LZ忽悠进来了,那就好好的享受一顿算法大餐吧.不过LZ丑话说前面哦,这篇文章应该能让各位彻底理解标记/清除算法,不过倘若各位猿友不能在五分钟内 ...
- GC算法精解(五分钟让你彻底明白标记/清除算法)
GC算法精解(五分钟让你彻底明白标记/清除算法) 相信不少猿友看到标题就认为LZ是标题党了,不过既然您已经被LZ忽悠进来了,那就好好的享受一顿算法大餐吧.不过LZ丑话说前面哦,这篇文章应该能让各位彻底 ...
- JVM内存管理之GC算法精解(五分钟让你彻底明白标记/清除算法)
相信不少猿友看到标题就认为LZ是标题党了,不过既然您已经被LZ忽悠进来了,那就好好的享受一顿算法大餐吧.不过LZ丑话说前面哦,这篇文章应该能让各位彻底理解标记/清除算法,不过倘若各位猿友不能在五分钟内 ...
- 20165219 《Java程序设计》实验二(Java开发环境的熟悉)实验报告
20165219 <Java程序设计>实验二(Java开发环境的熟悉)实验报告 一.实验报告封面 课程:Java程序设计 班级:1652班 姓名:王彦博 学号:20165219 成绩: 指 ...
- UT之最后一测
经过前面几次文章的分享的UT的相关知识,今天接着分享UT相关最后一测文章,希望对大家在UT的学习中有一点点的帮助. Spring集成测试 有时候我们需要在跑起来的Spring环境中验证,Spring ...
- iOS逆向工程之KeyChain与Snoop-it
今天博客的主题是Keychain, 在本篇博客中会通过一个登陆的Demo将用户名密码存入到KeyChain中,并且查看一下KeyChain中存的是什么东西,把这些内容给导出来.当然本篇博客的重点不是如 ...
- Nodejs之MEAN栈开发(七)---- 用Angular创建单页应用(下)
上一节我们走通了基本的SPA基础结构,这一节会更彻底的将后端的视图.路由.控制器全部移到前端.篇幅比较长,主要分页面改造.使用AngularUI两大部分以及一些优化路由.使用Angular的其他指令的 ...
- delphi.memory.分配及释放---New/Dispose, GetMem/FreeMem及其它函数的区别与相同
我估摸着内存分配+释放是个基础函数,有些人可能没注意此类函数或细究,但我觉得还是弄明白的好. 介绍下面内存函数前,先说一下MM的一些过程,如不关心可忽略: TMemoryManager = recor ...
- angular学习的一些小笔记(中)之表单验证
表单验证 我去,我感觉我这个人其实还是一个很傻逼的一个人,老是因为拼错了一个单词或者怎么样就浪费我很长时间,这样真的不行不行,要正确对待这个问题,好了,说正题吧,angular也有表单验证minlen ...
随机推荐
- CSS 阴影效果
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- NGK——解决区块链用户之“难”
自比特币诞生以来,区块链行业已发展十余年,而且在在金融.民生.司法存证.供应链协同.税务发票.版权保护等领域得到一定程度的应用,但大多属于边缘业务,以探索试点为主,应用深度和广度不足.为什么会这样?是 ...
- node应用层中间件使用
var express = require("express") var path = require("path") var app = express() ...
- 8.Vue组件三---slot插槽
主要内容: 1. 什么是插槽 2. 组件的插槽 3. 插槽的使用方法 4. 插槽的具名 5. 变量的作用域 6. slot的作用域 一. 什么是插槽呢? 1. 生活中的插槽有哪些呢? usb插槽, ...
- 2020年12月-第02阶段-前端基础-CSS Day05
CSS Day05 1. 学成在线页面制作 理解 能够说写单页面我们基本的流程 能说出常见的css初始化语句 能说出我们CSS属性书写顺序 应用 能利用ps切图 能引入外部样式表 能把psd文件转换为 ...
- go中waitGroup源码解读
waitGroup源码刨铣 前言 WaitGroup实现 noCopy state1 Add Wait 总结 参考 waitGroup源码刨铣 前言 学习下waitGroup的实现 本文是在go ve ...
- javascript中的Strict模式
目录 简介 使用Strict mode strict mode的新特性 强制抛出异常 简化变量的使用 简化arguments 让javascript变得更加安全 保留关键字和function的位置 总 ...
- IPFS挖矿必须要托管吗?
IPFS 本质上只是一个人人使用的协议,而 Filecoin 是 IPFS 的激励层,大家平时说的 IPFS 挖矿,其实就是挖 Filecoin.而提到IPFS 就不得不说到矿机托管的问题. 点击了解 ...
- apktool 回编译报错:No resource identifier found for attribute 'xxxxxx' in package 'android' W:
C:\xxxx\app-release\res\layout-v26\xxxx.xml:5: error: No resource identifier found for attribute 'xx ...
- 【Azure 应用服务】App Service 在使用GIt本地部署,上传代码的路径为/home/site/repository,而不是站点的根目录/home/site/wwwroot。 这个是因为什么?
问题描述 App Service 在使用GIt本地部署,上传代码的路径为/home/site/repository,而不是站点的根目录/home/site/wwwroot. 这个是因为什么? 并且通过 ...