spring boot 單元測試JUnit5使用斷言Assertions和假定Assumptions、嵌套、參數(shù)測試
本文基于spirng boot 2.7.11, 大家注意自己的版本
Spring Boot 2.2.0 版本開始引入 JUnit 5 作為單元測試默認(rèn)庫
SpringBoot 2.4 以上版本移除了默認(rèn)對 Vintage 的依賴
源碼地址:https://gitcode.net/qq_39339588/springboot.git
1. 標(biāo)記單元測試類和方法
@SpringBootTest注解,可以標(biāo)記為測試類
@Test注解標(biāo)記是測試方法
@DisplayName注解是標(biāo)記個(gè)名稱
@SpringBootTest
public class HelloWorldTest {
/**
* 斷言基礎(chǔ)
*/
@Test
@DisplayName("簡單斷言")
public void simple() {
}
}
2. 斷言的簡單使用
1)基本類型和對象的斷言
/**
* 基本類型和對象的斷言
*/
@Test
@DisplayName("簡單斷言")
public void simple() {
Assertions.assertEquals(3, 1 + 2, "不相等");
Assertions.assertNotEquals(3, 1 + 1, "相等了");
//斷言對象
Assertions.assertNotSame(new Object(), new Object(), "對象相同");
Object o = new Object();
Assertions.assertSame(o, o, "斷言不相等了");
Assertions.assertFalse(1 > 2);
Assertions.assertTrue(1 < 2);
Assertions.assertNull(null);
Assertions.assertNotNull(new Object());
}
2)數(shù)組斷言
@DisplayName("數(shù)組斷言")
@Test
public void array() {
// 斷言兩個(gè)數(shù)組對象不相等
Assertions.assertNotEquals(new int[]{1, 2}, new int[]{1, 2});
}
3)組合斷言
@DisplayName("組合斷言")
@Test
public void all() {
// 斷言所多個(gè)斷言方法都正確
Assertions.assertAll("組合斷言",
() -> Assertions.assertEquals(2, 1 + 1),
() -> Assertions.assertTrue(1 > 0)
);
}
4)異常斷言
@DisplayName("異常斷言")
@Test
public void exception() {
// 斷言方法 會出現(xiàn)異常
ArithmeticException arithmeticException = Assertions.assertThrows(ArithmeticException.class,
() -> System.out.println(1 % 0));
arithmeticException.printStackTrace();
}
5)超時(shí)斷言
@DisplayName("超時(shí)斷言")
@Test
public void timeOut() {
// 斷言方法執(zhí)行不會超過1秒
Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}
6)快速失敗斷言
@DisplayName("快速失敗")
@Test
public void failFast() {
// 觸發(fā)快速失敗后
AssertionFailedError ex = Assertions.assertThrows(AssertionFailedError.class, () -> {
Assertions.fail("觸發(fā)快速失敗");
});
ex.printStackTrace();
}
3. 前置條件斷言,假設(shè)假定
執(zhí)行斷言的前提條件,如果假定成功true,才會去執(zhí)行
/**
* 前置條件,假設(shè)假定
*/
@DisplayName("前置條件")
@Test
public void assuming() {
String evn = "dev";
// 執(zhí)行斷言的前提條件,如果假定成功true,才會去執(zhí)行下一行,如果假定失敗,下邊的就不執(zhí)行了
Assumptions.assumeTrue(Objects.equals(evn, "dev"));
Assumptions.assumeFalse(Objects.equals(evn, "prod"));
System.out.println("dev和不是prod,執(zhí)行了");
Assumptions.assumeTrue(Objects.equals(evn, "dev1"));
System.out.println("dev1,這句會忽略,不會執(zhí)行輸出");
}
/**
* 假定成功true,才會去執(zhí)行 執(zhí)行器方法
*/
@DisplayName("前置條件That")
@Test
public void assumingThat() {
// 假定成功,才會去執(zhí)行 執(zhí)行器方法
Assumptions.assumingThat(
Objects.equals("dev", "dev"), () -> {
System.out.println("in dev");
}
);
}
4. 嵌套@Nested
內(nèi)部嵌套,每次會先走外層的beforeEach,在走內(nèi)部的beforeEach;支持嵌套的嵌套
@DisplayName("單元測試示例")
@SpringBootTest
public class HelloWorldTest {
Stack<Object> stack;
/**
* 內(nèi)部嵌套,每次會先走外層的beforeEach,在走內(nèi)部的beforeEach
*/
@DisplayName("內(nèi)部嵌套測試")
@Nested
class InnerTest {
@BeforeEach
void newStack() {
// 創(chuàng)建一個(gè)棧對象
stack = new Stack<>();
}
@DisplayName("檢查是否為空")
@Test
void isEmpty() {
Assertions.assertTrue(stack.isEmpty());
}
@DisplayName("拋出一個(gè)棧pop異常")
@Test
void throwExceptionWhenPop() {
Assertions.assertThrows(EmptyStackException.class, stack::pop);
}
@DisplayName("拋出一個(gè)棧peek異常")
@Test
void throwExceptionWhenPeek() {
Assertions.assertThrows(EmptyStackException.class, stack::peek);
}
@Nested
@DisplayName("內(nèi)部的內(nèi)部嵌套測試")
class InnerInnerTest {
String item = "item";
@BeforeEach
void pushAnItem() {
// 放入一個(gè)元素
stack.push(item);
}
@DisplayName("不為空了")
@Test
void isNotEmpty() {
Assertions.assertFalse(stack.isEmpty());
}
@DisplayName("取出一個(gè)來,用pop")
@Test
void returnItemByPop() {
Assertions.assertEquals(item, stack.pop());
Assertions.assertTrue(stack.isEmpty());
}
@DisplayName("取出一個(gè)來,用peek")
@Test
void returnItemByPeek() {
Assertions.assertEquals(item, stack.peek());
Assertions.assertFalse(stack.isEmpty());
}
}
}
5. 參數(shù)化測試
參數(shù)化測試,讓單元測試的方法支持接收傳入的參數(shù)
將可以使用不同的參數(shù)進(jìn)行多次單元測試,有多少套參數(shù),就執(zhí)行多少次單元測試
而不需要每新增一個(gè)參數(shù)就新增一個(gè)單元測試,省去了很多冗余代碼
1)@ValueSource注解,基本類型入?yún)?/h6>
/**
* @ValueSource:為參數(shù)化測試指定入?yún)碓?,支持八大基礎(chǔ)類以及String類型,Class類型
* @param params
*/
@DisplayName("參數(shù)化測試,值來源")
@ParameterizedTest
@ValueSource(strings = {"a", "b", "c"})
public void parameterizedTest(String params) {
// 會執(zhí)行3次,分別執(zhí)行a,b,c
System.out.println(params);
Assertions.assertTrue(StringUtils.isNotBlank(params));
}
2)@NullSource注解,null入?yún)?/h6>
/**
* null入?yún)? *
* @param params
*/
@DisplayName("null的入?yún)?)
@ParameterizedTest
@NullSource
public void testNull(String params) {
Assertions.assertNull(params);
}
3)@EnumSource注解,枚舉入?yún)?/h6>
/**
* 枚舉入?yún)?,會把枚舉遍歷一遍,分別執(zhí)行一次方法
*/
@DisplayName("枚舉的入?yún)?)
@ParameterizedTest
@EnumSource(TestEnum.class)
public void testEnumSource(TestEnum params) {
System.out.println("state:" + params.state + ",msg:" + params.msg);
// state:1,msg:好
// state:2,msg:不好
Assertions.assertNotNull(params);
}
/**
* 枚舉類
*/
@Getter
@AllArgsConstructor
public enum TestEnum {
GOOD(1, "好"),
BAD(2, "不好");
int state;
String msg;
}
4)@CsvSource注解,多參數(shù)入?yún)?/h6>
@DisplayName("參數(shù)化測試,@CsvSource")
@ParameterizedTest
@CsvSource({"a,b,c"})
void parameterizedTestCsvSource(String a, String b, String c) {
// 默認(rèn)使用逗號分隔
System.out.println(a + b + c);
Assertions.assertNotNull(a);
}
@DisplayName("參數(shù)化測試,@CsvSource2")
@ParameterizedTest
@CsvSource(value = {"a,b,c-D"}, delimiterString = "-")
void parameterizedTestCsvSource2(String abc, String d) {
// 默認(rèn)使用逗號分隔,指定分隔符"-"
System.out.println(abc);
System.out.println(d);
Assertions.assertNotNull(abc);
// a,b,c
// D
}
5)@CsvFileSource,參數(shù)文件入?yún)?/h6>
可以把文件中的內(nèi)容當(dāng)做參數(shù),批量來執(zhí)行,下邊附帶了my.csv和my2.csv文件內(nèi)容
注意,要在測試目錄下,新建resources目錄,再放入my.csv和my2.csv文件
/**
* 參數(shù)文件
*
* @param methodName
*/
@DisplayName("參數(shù)化測試,@CsvFileSource")
@ParameterizedTest
@CsvFileSource(resources = "/my.csv")
void parameterizedTestWithCsvFileSource(String methodName) {
// 測試目錄下,新建resources目錄
System.out.println(methodName);
Assertions.assertNotNull(methodName);
}
@DisplayName("參數(shù)化測試,@CsvFileSource")
@ParameterizedTest
@CsvFileSource(resources = "/my2.csv", delimiterString = "|")
void parameterizedTestWithCsvFileSource2(String name, Integer age) {
// 測試目錄下,新建resources目錄
System.out.println(name + ":" + age);
Assertions.assertNotNull(name);
// a:18
// b:17
// c:20
}
/**
* @ValueSource:為參數(shù)化測試指定入?yún)碓?,支持八大基礎(chǔ)類以及String類型,Class類型
* @param params
*/
@DisplayName("參數(shù)化測試,值來源")
@ParameterizedTest
@ValueSource(strings = {"a", "b", "c"})
public void parameterizedTest(String params) {
// 會執(zhí)行3次,分別執(zhí)行a,b,c
System.out.println(params);
Assertions.assertTrue(StringUtils.isNotBlank(params));
}
/**
* null入?yún)? *
* @param params
*/
@DisplayName("null的入?yún)?)
@ParameterizedTest
@NullSource
public void testNull(String params) {
Assertions.assertNull(params);
}
3)@EnumSource注解,枚舉入?yún)?/h6>
/**
* 枚舉入?yún)?,會把枚舉遍歷一遍,分別執(zhí)行一次方法
*/
@DisplayName("枚舉的入?yún)?)
@ParameterizedTest
@EnumSource(TestEnum.class)
public void testEnumSource(TestEnum params) {
System.out.println("state:" + params.state + ",msg:" + params.msg);
// state:1,msg:好
// state:2,msg:不好
Assertions.assertNotNull(params);
}
/**
* 枚舉類
*/
@Getter
@AllArgsConstructor
public enum TestEnum {
GOOD(1, "好"),
BAD(2, "不好");
int state;
String msg;
}
4)@CsvSource注解,多參數(shù)入?yún)?/h6>
@DisplayName("參數(shù)化測試,@CsvSource")
@ParameterizedTest
@CsvSource({"a,b,c"})
void parameterizedTestCsvSource(String a, String b, String c) {
// 默認(rèn)使用逗號分隔
System.out.println(a + b + c);
Assertions.assertNotNull(a);
}
@DisplayName("參數(shù)化測試,@CsvSource2")
@ParameterizedTest
@CsvSource(value = {"a,b,c-D"}, delimiterString = "-")
void parameterizedTestCsvSource2(String abc, String d) {
// 默認(rèn)使用逗號分隔,指定分隔符"-"
System.out.println(abc);
System.out.println(d);
Assertions.assertNotNull(abc);
// a,b,c
// D
}
5)@CsvFileSource,參數(shù)文件入?yún)?/h6>
可以把文件中的內(nèi)容當(dāng)做參數(shù),批量來執(zhí)行,下邊附帶了my.csv和my2.csv文件內(nèi)容
注意,要在測試目錄下,新建resources目錄,再放入my.csv和my2.csv文件
/**
* 參數(shù)文件
*
* @param methodName
*/
@DisplayName("參數(shù)化測試,@CsvFileSource")
@ParameterizedTest
@CsvFileSource(resources = "/my.csv")
void parameterizedTestWithCsvFileSource(String methodName) {
// 測試目錄下,新建resources目錄
System.out.println(methodName);
Assertions.assertNotNull(methodName);
}
@DisplayName("參數(shù)化測試,@CsvFileSource")
@ParameterizedTest
@CsvFileSource(resources = "/my2.csv", delimiterString = "|")
void parameterizedTestWithCsvFileSource2(String name, Integer age) {
// 測試目錄下,新建resources目錄
System.out.println(name + ":" + age);
Assertions.assertNotNull(name);
// a:18
// b:17
// c:20
}
/**
* 枚舉入?yún)?,會把枚舉遍歷一遍,分別執(zhí)行一次方法
*/
@DisplayName("枚舉的入?yún)?)
@ParameterizedTest
@EnumSource(TestEnum.class)
public void testEnumSource(TestEnum params) {
System.out.println("state:" + params.state + ",msg:" + params.msg);
// state:1,msg:好
// state:2,msg:不好
Assertions.assertNotNull(params);
}
/**
* 枚舉類
*/
@Getter
@AllArgsConstructor
public enum TestEnum {
GOOD(1, "好"),
BAD(2, "不好");
int state;
String msg;
}
@DisplayName("參數(shù)化測試,@CsvSource")
@ParameterizedTest
@CsvSource({"a,b,c"})
void parameterizedTestCsvSource(String a, String b, String c) {
// 默認(rèn)使用逗號分隔
System.out.println(a + b + c);
Assertions.assertNotNull(a);
}
@DisplayName("參數(shù)化測試,@CsvSource2")
@ParameterizedTest
@CsvSource(value = {"a,b,c-D"}, delimiterString = "-")
void parameterizedTestCsvSource2(String abc, String d) {
// 默認(rèn)使用逗號分隔,指定分隔符"-"
System.out.println(abc);
System.out.println(d);
Assertions.assertNotNull(abc);
// a,b,c
// D
}
5)@CsvFileSource,參數(shù)文件入?yún)?/h6>
可以把文件中的內(nèi)容當(dāng)做參數(shù),批量來執(zhí)行,下邊附帶了my.csv和my2.csv文件內(nèi)容
注意,要在測試目錄下,新建resources目錄,再放入my.csv和my2.csv文件
/**
* 參數(shù)文件
*
* @param methodName
*/
@DisplayName("參數(shù)化測試,@CsvFileSource")
@ParameterizedTest
@CsvFileSource(resources = "/my.csv")
void parameterizedTestWithCsvFileSource(String methodName) {
// 測試目錄下,新建resources目錄
System.out.println(methodName);
Assertions.assertNotNull(methodName);
}
@DisplayName("參數(shù)化測試,@CsvFileSource")
@ParameterizedTest
@CsvFileSource(resources = "/my2.csv", delimiterString = "|")
void parameterizedTestWithCsvFileSource2(String name, Integer age) {
// 測試目錄下,新建resources目錄
System.out.println(name + ":" + age);
Assertions.assertNotNull(name);
// a:18
// b:17
// c:20
}
可以把文件中的內(nèi)容當(dāng)做參數(shù),批量來執(zhí)行,下邊附帶了my.csv和my2.csv文件內(nèi)容
注意,要在測試目錄下,新建resources目錄,再放入my.csv和my2.csv文件
/**
* 參數(shù)文件
*
* @param methodName
*/
@DisplayName("參數(shù)化測試,@CsvFileSource")
@ParameterizedTest
@CsvFileSource(resources = "/my.csv")
void parameterizedTestWithCsvFileSource(String methodName) {
// 測試目錄下,新建resources目錄
System.out.println(methodName);
Assertions.assertNotNull(methodName);
}
@DisplayName("參數(shù)化測試,@CsvFileSource")
@ParameterizedTest
@CsvFileSource(resources = "/my2.csv", delimiterString = "|")
void parameterizedTestWithCsvFileSource2(String name, Integer age) {
// 測試目錄下,新建resources目錄
System.out.println(name + ":" + age);
Assertions.assertNotNull(name);
// a:18
// b:17
// c:20
}
my.csv文件
1
2
3
4
my2.csv文章來源:http://www.zghlxwxcb.cn/news/detail-513282.html
a|18
b|17
c|20
6)@MethodSource注解,方法返回值入?yún)?/h6>
把一個(gè)方法的返回值,作為測試用例方法的參數(shù)文章來源地址http://www.zghlxwxcb.cn/news/detail-513282.html
/**
* 動(dòng)態(tài)參數(shù)
*/
@DisplayName("參數(shù)化測試,方法來源")
@ParameterizedTest
@MethodSource("method1") // 如果不指定方法名稱,會去找與自己相同名稱的靜態(tài)方法
public void parameterizedTestWithMethodParam(String params) {
// MethodSource("method1") 接收這個(gè)方法的返回值,之后會執(zhí)行2次,a,b
System.out.println(params);
Assertions.assertNotNull(params);
}
static Stream<String> method1() {
return Stream.of("a", "b");
}
@DisplayName("參數(shù)化測試,方法來源,多參數(shù)")
@ParameterizedTest
@MethodSource // 如果不指定方法名稱,會去找相同名稱的靜態(tài)方法
public void parameterizedTestWithMethodMultipleParams(String name, Integer age) {
System.out.println(name + ":" + age);
// a:18
// b:17
// c:20
Assertions.assertNotNull(name);
}
static Stream<Arguments> parameterizedTestWithMethodMultipleParams() {
return Stream.of(
Arguments.arguments("a", 18),
Arguments.arguments("b", 17),
Arguments.arguments("c", 20)
);
}
7) @ArgumentsSource注解,自定義參數(shù)入?yún)?/h6>
/**
* 實(shí)現(xiàn)ArgumentsProvider接口,并在測試方法上使用@ArgumentsSource注解。
*/
@DisplayName("@ArgumentsSource注解")
@ParameterizedTest
@ArgumentsSource(MyArgumentsProvider.class)
public void testArgumentsSource(String name, int age) {
System.out.println(name + ":" + age);
// aa:18
// bb:20
// cc:30
Assertions.assertNotNull(name);
}
把一個(gè)方法的返回值,作為測試用例方法的參數(shù)文章來源地址http://www.zghlxwxcb.cn/news/detail-513282.html
/**
* 動(dòng)態(tài)參數(shù)
*/
@DisplayName("參數(shù)化測試,方法來源")
@ParameterizedTest
@MethodSource("method1") // 如果不指定方法名稱,會去找與自己相同名稱的靜態(tài)方法
public void parameterizedTestWithMethodParam(String params) {
// MethodSource("method1") 接收這個(gè)方法的返回值,之后會執(zhí)行2次,a,b
System.out.println(params);
Assertions.assertNotNull(params);
}
static Stream<String> method1() {
return Stream.of("a", "b");
}
@DisplayName("參數(shù)化測試,方法來源,多參數(shù)")
@ParameterizedTest
@MethodSource // 如果不指定方法名稱,會去找相同名稱的靜態(tài)方法
public void parameterizedTestWithMethodMultipleParams(String name, Integer age) {
System.out.println(name + ":" + age);
// a:18
// b:17
// c:20
Assertions.assertNotNull(name);
}
static Stream<Arguments> parameterizedTestWithMethodMultipleParams() {
return Stream.of(
Arguments.arguments("a", 18),
Arguments.arguments("b", 17),
Arguments.arguments("c", 20)
);
}
/**
* 實(shí)現(xiàn)ArgumentsProvider接口,并在測試方法上使用@ArgumentsSource注解。
*/
@DisplayName("@ArgumentsSource注解")
@ParameterizedTest
@ArgumentsSource(MyArgumentsProvider.class)
public void testArgumentsSource(String name, int age) {
System.out.println(name + ":" + age);
// aa:18
// bb:20
// cc:30
Assertions.assertNotNull(name);
}
到了這里,關(guān)于spring boot 單元測試JUnit5使用斷言Assertions和假定Assumptions、嵌套、參數(shù)測試的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!