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 断言办法做了补充。
参考
https://junit.org/junit5/docs…
https://assertj.github.io/doc/