Junit单元测试
1、JUnit5 的变化
Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库
作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同。由三个不同子项目的几个不同模块组成。
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。
JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行。
JUnit Vintage: 提供Junit3和Junit4的测试引擎
注意:
SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容junit4需要自行引入(不能使用junit4的功能 @Test)
JUnit 5’s Vintage Engine Removed from spring-boot-starter-test,如果需要继续兼容junit4需要自行引入vintage
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
@SpringBootTest
class Springboot03ApplicationTests {
@Test
void contextLoads() {
}
}
以前:
@SpringBootTest + @RunWith(SpringTest.class)
SpringBoot整合Junit以后。
- 编写测试方法:@Test标注(注意需要使用junit5版本的注解)
- Junit类具有Spring的功能,@Autowired、比如 @Transactional 标注测试方法,测试完成后自动回滚
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
@SpringBootTest
@Slf4j
class Springboot03ApplicationTests {
@Autowired
JdbcTemplate jdbcTemplate;
@Test
void contextLoads() {
Integer count = jdbcTemplate.queryForObject("select count(*) from user ", Integer.class);
log.info("user表中{}条数据",count);
}
}
2、Junit5的常用注解
JUnit5的注解与JUnit4的注解有所变化
1、@Test注解
●@Test :表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
//普通测试
public class Junit5Test {
@Test
@DisplayName("这是一个简单的Junit测试")
public void Test(){
System.out.println(1);
}
}
2、@DisplayName注解
●@DisplayName :为测试类或者测试方法设置别名
//该注解可以使用标识在类上,也可以表示在某个方法上
@DisplayName("这是一个测试类")
public class Junit5Test {
@Test
@DisplayName("这是一个简单的Junit测试")
public void Test(){
System.out.println(1);
}
}
控制台效果:
3、@BeforeEach,@AfterEach
●@BeforeEach :表示在每个单元测试之前执行
●@AfterEach :表示在每个单元测试之后执行
@DisplayName("这是一个测试类")
public class Junit5Test {
@Test
@DisplayName("这是一个简单的Junit测试")
public void Test() {
System.out.println(1);
}
@Test
@BeforeEach
public void testBeforeEach() {
System.out.println("在方法测试开始之前...");
}
@Test
@AfterEach
public void testAfterEach() {
System.out.println("在方法测试结束后...");
}
}
执行Test方法后,控制台输出:
在方法测试开始之前...
1
在方法测试结束后...
4、@BeforeAll、@AfterAll
●@BeforeAll :表示在所有单元测试之前执行
●@AfterAll :表示在所有单元测试之后执行
@DisplayName("这是一个测试类")
public class Junit5Test {
@Test
@DisplayName("这是一个简单的Junit测试")
public void Test() {
System.out.println(1);
}
@Test
@DisplayName("第二个Junit测试方法")
public void test2(){
System.out.println(2);
}
@Test
@BeforeEach
public void testBeforeEach() {
System.out.println("在方法测试开始之前...");
System.out.println("testBeforeEach方法");
}
@Test
@AfterEach
public void testAfterEach() {
System.out.println("在方法测试结束后...");
System.out.println("testAfterEach方法");
}
@Test
@BeforeAll
public static void testBeforeAll(){
System.out.println("在所有的测试方法开始之前...");
}
@Test
@AfterAll
public static void testAfterAll(){
System.out.println("在所有的测试方法执行完毕后...");
}
}
总结:
@BeforeEach和@AfterEach针对的是每一个方法(包括其本身)的开始和结束,@BeforeAll 和@AfterAll针对的是整个测试类的开始和结束,所以@BeforeAll 和@AfterAll的方法需要设置为static类型。
5、@Timeout
●@Timeout :表示测试方法运行如果超过了指定时间将会返回错误
@Test
//value表示数值大小,unit表示时间的类型(秒、毫秒、分、时等)
@Timeout(value = 500,unit = TimeUnit.MILLISECONDS)
public void timeoutTest() throws InterruptedException {
Thread.sleep(600); //我们让程序睡眠600ms(超过500ms)
}
报错:
java.util.concurrent.TimeoutException: timeoutTest() timed out after 500 milliseconds
//表示该方法的运行时间已经超过了我们所设置的500ms
6、@RepeatTest
●@RepeatedTest :表示方法可重复执行,使用该注解后无需为方法标识@Test注解
@RepeatedTest(5)
public void repeatTest(){
System.out.println(1);
}
7、@Disable
●@Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
8、@ParameterizedTest
除去使用@ParameterizedTest注解之外,还需要与下列注解配合使用才能够实现参数化的测试
@ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
@NullSource: 表示为参数化测试提供一个null的入参
@EnumSource: 表示为参数化测试提供一个枚举入参
@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流),指定方法必须为静态方法
@ParameterizedTest
@Test
@ValueSource(ints = {1, 2, 3, 4, 5})
public void paramTest(int i) {
System.out.println(i);
}
@ParameterizedTest
@Test
@ValueSource(strings = {"carl","method","long"})
public void paramTest(String str) {
System.out.println(str);
}
@ParameterizedTest
@Test
@ValueSource(classes = {String.class, Array.class, Map.class})
public void paramTest(Class cls) {
System.out.println(cls);
}
@Test
@ParameterizedTest
@MethodSource("getStream")
public void testMethodSource(String str){
System.out.println(str);
}
public static Stream<String> getStream(){
return Stream.of("springboot","junit");
}
9、@SpringBootTest
●@SpringBootTest:用于测试类,通过此注解可以使类内部调用SpringBoot组件
@SpringBootTest
@DisplayName("这是一个测试类")
public class Junit5Test {
@Test
@DisplayName("这是一个简单的Junit测试")
public void Test() {
System.out.println(1);
}
}
3、断言(assertions)
断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。JUnit 5 内置的断言可以分成如下几个类别:
检查业务逻辑返回的数据是否合理。
**所有的测试运行结束以后,会有一个详细的测试报告;**测试报告会显示那些测试成功,那些测试失败
点击选择clean和test点击开始按钮,就会生成测试报告
下面的图片即为报告的内容
1、简单断言
用来对单个值进行简单的验证。如:
方法 | 说明 |
assertEquals | 判断两个对象或两个原始类型是否相等 |
assertNotEquals | 判断两个对象或两个原始类型是否不相等 |
assertSame | 判断两个对象引用是否指向同一个对象 |
assertNotSame | 判断两个对象引用是否指向不同的对象 |
assertTrue | 判断给定的布尔值是否为 true |
assertFalse | 判断给定的布尔值是否为 false |
assertNull | 判断给定的对象引用是否为 null |
assertNotNull | 判断给定的对象引用是否不为 null |
@DisplayName("断言测试类")
public class AssertionTest {
@DisplayName("测试简单断言")
@Test
public void testSimple(){
assertEquals(6,calc(2,3),"得不到期望的值"); //报错
assertEquals(6,calc(3,3));
assertNotEquals(5,calc(2,3),"得到了不期望的值"); //报错
assertNotEquals(5,calc(3,3));
assertSame(new Object(),new Object(),"两个对象不同"); //报错
Object obj = new Object();
assertSame(obj,obj);
assertFalse(1 > 2);
assertFalse(1 < 2,"期望得到false(当前结果为true)"); //报错
assertTrue(1 > 2,"期望得到true(当前结果不为false)");
assertTrue(1 < 2); //报错
assertNull(null);
assertNull(obj,"期望参数为null(当前参数不为null)"); //报错
assertNotNull(new Object());
assertNotNull(null,"期望参数为null(当前参数不为null)"); //报错
}
public int calc(int a,int b){
return a + b;
}
}
2、数组断言
@DisplayName("数组断言")
@Test
public void testArrayAssertion(){
assertArrayEquals(new int[]{2,1},new int[]{2,2},"数组元素不相同");
}
3、组合断言
组合断言通过lambda表达式来实现
@DisplayName("组合断言")
@Test
public void allAssertion(){
assertAll("算术或逻辑运算出错",
() -> assertEquals(3,1 + 2),
() -> assertTrue(true && false));
}
4、异常断言
@Test
@DisplayName("异常测试")
public void exceptionTest() {
ArithmeticException exception = Assertions.assertThrows(
//扔出断言异常
ArithmeticException.class, () -> System.out.println(1 % 0));
}
5、超时断言
@Test
@DisplayName("超时断言")
public void timeoutAssertion(){
assertTimeout(Duration.ofMillis(1000),()-> Thread.sleep(2000));
}
6、失败断言
@Test
@DisplayName("失败断言")
public void failTest(){
fail("This method should fail");
}
4、前置条件
JUnit 5 中的前置条件(assumptions【假设】)类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。
@DisplayName("前置条件")
public class AssumptionsTest {
private final String environment = "DEV";
@Test
@DisplayName("simple")
public void simpleAssume() {
assumeTrue(Objects.equals(this.environment, "DEV"));
assumeFalse(() -> Objects.equals(this.environment, "PROD"));
}
@Test
@DisplayName("assume then do")
public void assumeThenDo() {
assumingThat(
Objects.equals(this.environment, "DEV"),
() -> System.out.println("In DEV")
);
}
}
5、嵌套测试
JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制。
内层可以使用调用外层的@BeforeEach/@BeforeAll和@AfterEach/@AfterAll注解,而外层不能调用内层的@BeforeEach/@BeforeAll和@AfterEach/@AfterAll注解
@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());
}
@Test
@DisplayName("throws EmptyStackException when popped")
void throwsExceptionWhenPopped() {
assertThrows(EmptyStackException.class, stack::pop);
}
@Test
@DisplayName("throws EmptyStackException when peeked")
void throwsExceptionWhenPeeked() {
assertThrows(EmptyStackException.class, stack::peek);
}
@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());
}
@Test
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPopped() {
assertEquals(anElement, stack.pop());
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("returns the element when peeked but remains not empty")
void returnElementWhenPeeked() {
assertEquals(anElement, stack.peek());
assertFalse(stack.isEmpty());
}
}
}