SpringBoot之测试和断言

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的注解有所变化

https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations

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);
    }
}

控制台效果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6KRuojDs-1652788111664)(C:\Users\周畅\AppData\Roaming\Typora\typora-user-images\image-20220517164931586.png)]

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("在所有的测试方法执行完毕后...");
    }

}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k2wsXQU6-1652788111666)(C:\Users\周畅\AppData\Roaming\Typora\typora-user-images\image-20220517170438601.png)]

总结:@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);
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zkGso0WP-1652788111667)(C:\Users\周畅\AppData\Roaming\Typora\typora-user-images\image-20220517171151788.png)]

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);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BprpeY9j-1652788111667)(C:\Users\周畅\AppData\Roaming\Typora\typora-user-images\image-20220517171656115.png)]

3、断言(assertions)

断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。JUnit 5 内置的断言可以分成如下几个类别:

检查业务逻辑返回的数据是否合理。

**所有的测试运行结束以后,会有一个详细的测试报告;**测试报告会显示那些测试成功,那些测试失败

点击选择clean和test点击开始按钮,就会生成测试报告

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tkn1lL0t-1652788111668)(C:\Users\周畅\AppData\Roaming\Typora\typora-user-images\image-20220517191823055.png)]

下面的图片即为报告的内容

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OUOTQHNq-1652788111670)(C:\Users\周畅\AppData\Roaming\Typora\typora-user-images\image-20220517191959162.png)]

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());
            }
        }
    }

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值