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]类中的静态方法。
分组断言:
多个条件同时满足时才断言胜利
@Testvoid groupedAssertions() { Person person = new Person(); Assertions.assertAll("person", () -> assertEquals("niu", person.getName()), () -> assertEquals(18, person.getAge()) );}
异样断言:
Junit4时须要应用rule形式,junit5提供了assertThrows更优雅的异样断言
@Testvoid 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应用办法十分相似
@Testvoid 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");}@Testvoid 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();}@Testvoid 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");}@Testvoid 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/