刷了多道 leetcode 后发觉每次写 main 测试很麻烦,因此稍微对项目进行了调整,转为使用 junit 进行测试。
封面《ハミダシクリエイティブ》
前言
在 leetcode 上进行 DEBUG 需要充值 VIP,因此我一般都是在线下进行 DEBUG,然而如果为每道题都 DEBUG 则需要编写不同的 main 函数,且切换数据的时候比较麻烦,因此使用 junit 来进行测试,方便每次 debug。
JUnit5 介绍
JUnit 是基于 Java 的一个单元测试框架,JUnit5 一共由以下几个三部分组成
- JUnit Platform: 运行在 JVM 上的一个测试框架,定义了测试的 API,可以接入其他测试引擎
- JUnit Jupiter: 全新的编程和拓展的测试模型的组合,并为 JUnit Platform 提供测试引擎
- JUnit Vintage: 提供了 JUnit3、JUnit4 的测试引擎
Maven 中导入
参考官方给出的 JUnit5 项目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <dependencyManagement> <dependencies> <dependency> <groupId>org.junit</groupId> <artifactId>junit-bom</artifactId> <version>5.8.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
<dependencies> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <scope>test</scope> </dependency> </dependencies>
|
注解
以下是一些常见的测试注解,更多的测试注解以及其使用方法请翻阅文档
Annotation |
Description |
@Test |
描述一个方法是测试方法,与 JUnit4 不同的是此方法不申明任何属性 |
@ParameterizedTest |
表示该方法为参数化测试 |
@RepeatedTest |
表示该方法为重复测试 |
@TestFactory |
表示该方法为动态测试 |
@TestTemplate |
表示该方法为测试模板 |
@TestClassOrder |
表示 @Nested 标注的测试类的执行顺序 |
@TestMethodOrder |
表示测试方法的执行顺序 |
@TestInstance |
配置测试实例的生命周期 |
@DisplayName |
自定义测试的名称 |
@DisplayNameGeneration |
自定义测试名的生成方法 |
@BeforeEach |
在每个测试方法测试前执行 |
@AfterEach |
在每个测试方法执行后执行 |
@BeforeAll |
在所有测试方法测试前执行,注解的方法必须是 static |
@AfterAll |
在所有测试方法测试后执行,注解的方法必须是 static |
@Nested |
描述一个非静态的嵌套测试类 |
@Tag |
使用定义的标签去过滤一些测试 |
@Disabled |
忽略一些测试方法 |
@Timeout |
运行超过了时间会报错 |
@ExtendWith |
声明的方式拓展测试类 |
@RegisterExtension |
编程的方式拓展测试类 |
@TempDir |
使用临时目录进行测试 |
生命周期
一个标准的测试的生命周期如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.assumeTrue;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test;
class StandardTests {
@BeforeAll static void initAll() { }
@BeforeEach void init() { }
@Test void succeedingTest() { }
@Test void failingTest() { fail("a failing test"); }
@Test @Disabled("for demonstration purposes") void skippedTest() { }
@Test void abortedTest() { assumeTrue("abc".contains("Z")); fail("test should have been aborted"); }
@AfterEach void tearDown() { }
@AfterAll static void tearDownAll() { }
}
|
参数化测试
以目前的需求来说,刷 leetcode 最需要是参数化测试,因此本次重点就是几种常用的参数化测试
@ValueSource
@ValueSource
是最简单的数据来源之一,可以使用简单的数组来提供测试数据。支持的类型有 short
,byte
,int
,long
,float
,double
,char
,boolean
,java.lang.String
,java.lang.Class
。
1 2 3 4 5 6 7 8
| public class ValueSourceTest {
@ParameterizedTest @ValueSource(ints = { 1, 2, 3, 4 }) public void testLargerThanZero(int num) { assertTrue(num > 0); } }
|
对于 NULL 或者空数据则可以使用 @NullSource
、@EmptySource
、@NullAndEmptySource
@EnumSource
1 2 3 4 5 6 7 8
| public class EnumSourceTest {
@ParameterizedTest @EnumSource(names = { "DAYS", "HOURS" }) public void test(ChronoUnit unit) { assertTrue(EnumSet.of(ChronoUnit.DAYS, ChronoUnit.HOURS).contains(unit)); } }
|
@MethodSource
@MethonSource
允许你使用一个 Method 工厂来提供源,需要注意的是提供者必须是 static,返回的类型需要是 Stream
或者 Arguments
,或者是可以被 JUnit 转化为 Stream
的类型,比如 Collection
,DoubleStream
等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class MethodSourceTest {
@ParameterizedTest @MethodSource("stringProvider") public void test(String got, String expect) { assertEquals(got, expect); }
static Stream<Arguments> stringProvider() { return Stream.of( Arguments.of("apple","apple"), Arguments.of("banana","banana"), Arguments.of("pea","pea") ); } }
|
@CsvSource
@CsvSource
允许以分隔符分隔输入列表中的参数。默认的分隔是 ,
,但是可以通过 delimiterString
进行改变。
1 2 3 4 5 6 7 8 9 10 11
| public class CsvSourceTest { @ParameterizedTest @CsvSource({ "1,1,2", "2,2,4", "3,3,6" }) public void test(int add1,int add2,int expect){ assertEquals(add1+add2, expect); } }
|
@CsvFileSource
@CsvFileSource
可以使用 csv 文件进行测试。创建 test.csv
文件,如下填充
1 2 3 4 5
| add1,add2,expect 1,1,2 2,2,4 3,2,5 5,4,9
|
1 2 3 4 5 6 7 8
| public class CsvFileSourceTest {
@ParameterizedTest @CsvFileSource(resources = "./test.csv", numLinesToSkip = 1) public void test(int add1, int add2, int expect) { assertEquals(add1 + add2, expect); } }
|
@ArgumentsSource
@ArgumentsSource
可以使用 ArgumentsProvider
进行自定义。
1 2 3 4 5 6 7 8
| public class ArgumentsSourceTest {
@ParameterizedTest @ArgumentsSource(MyArgumentsProvider.class) public void testWithArgumentsSource(String argument) { assertNotNull(argument); } }
|
1 2 3 4 5 6 7 8
| public class MyArgumentsProvider implements ArgumentsProvider {
@Override public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception { return Stream.of("apple", "banana").map(Arguments::of); }
}
|
参数转换与增强
对于参数化测试的输入参数,JUnit 还能对输入参数的转化很增强进行定制,详细的可以翻阅文档,此处不再多做说明。
总结
JUnit 是一个很好的测试框架,能非常便利的编写测试,配合 leetcode 进行 DEBUG。
参考资料
JUnit
廖雪峰的 Java 教程