Spring Boot 单元测试

资料

Junit4使用

Junit4常用注解

@BeforeClass:

@BeforeClass注解的public void方法将会在所有测试方法执行之前执行。

@BeforeClass注解修饰的方法必须是静态方法。

@BeforeClass
public static void setUpBeforeClass() {
    System.out.println("@BeforeClass修饰的方法会在所有测试方法执行之前执行");
}

@AfterClass:

@AfterClass注解的public void方法将会在所有方法执行完毕之后执行。

@AfterClass注解修饰的方法必须是静态方法。

@AfterClass
public static void tearDownAfterClass() throws Exception {
    System.out.println("@AfterClass修饰的方法会在所有方法执行完毕之后执行");
}

@After:

@After注解的public void方法将会在每个测试方法执行之后执行一次。

@After注解修饰的方法必须不是静态方法。

@After
public void tearDown() throws Exception {
    System.out.println("@After修饰的方法会在每个测试方法执行之后执行一次");
}

@Ignore:

对包含测试类的类或@Test注解方法使用@Ignore注解将使被注解的类或方法不会被当做测试执行。

@Ignore("这个方法很傲娇,不想被测试")
@Test
public void test() {
    System.out.println("test...");
}

@Test:

@Test注解的public void方法将会被当做测试用例。

注解参数expected:定义测试方法应该抛出的异常,如果测试方法没有抛出异常或者抛出了一个不同的异常,测试失败。

注解参数timeout:如果测试运行时间长于该定义时间,测试失败(单位为毫秒)。

@Test(expected = RuntimeException.class, timeout = 1000)
public void test2() {
    System.out.println("test...");
    throw new RuntimeException("我错了。。。");
}

@Test(expected = RuntimeException.class)
public void test3() {
    System.out.println("test...");
}

@Test(timeout = 1000)
public void test4() throws InterruptedException {
    System.out.println("test...");
    Thread.sleep(2000);
}

Junit4单元测试执行顺序

通过org.junit.FixMethodOrder可以指定对应的org.junit.runners.MethodSorters排序策略

MethodSorters提供了三种策略:

  • NAME_ASCENDING(MethodSorter.NAME_ASCENDING): 按照方法名字典顺序
  • JVM(null): 按照JVM返回的顺序
  • DEFAULT(MethodSorter.DEFAULT: 默认,通过查看DEFAULT这个Comparator函数源码可知,按照方法名的hashCode值排序
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class Junit4Test {
}

Junit5使用

介绍

Junit5由来自三个不同子项目的几个不同模块组成。

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

  • JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。
  • JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行。
  • JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎。

从JUnit4迁移

  • 常用注解package:org.junit --> org.junit.jupiter.api
  • 断言工具package:org.junit.Assert --> org.junit.jupiter.api.Assertions
  • 假设工具package:org.junit.Assume --> org.junit.jupiter.api.Assumptions
  • @Before and @After no longer exist --> use @BeforeEach and @AfterEach instead.
  • @BeforeClass and @AfterClass no longer exist --> use @BeforeAll and @AfterAll instead.
  • @Ignore no longer exists --> use @Disabled or one of the other built-in execution conditions instead
  • @Category no longer exists --> use @Tag instead.
  • @RunWith no longer exists --> superseded by @ExtendWith.
  • @Rule and @ClassRule no longer exist --> superseded by @ExtendWith and @RegisterExtension

Junit5常用注解

Junit5一些常用注解说明,package位置org.junit.jupiter.api

  • @Test :表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
  • @ParameterizedTest :表示方法是参数化测试
  • @RepeatedTest :表示方法可重复执行
  • @TestFactory :测试工厂进行动态测试
  • @DisplayName :为测试类或者测试方法设置展示名称
  • @BeforeEach :表示在每个单元测试之前执行
  • @AfterEach :表示在每个单元测试之后执行
  • @BeforeAll :表示在所有单元测试之前执行
  • @AfterAll :表示在所有单元测试之后执行
  • @Tag :表示单元测试类别,类似于JUnit4中的@Categories
  • @Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
  • @Timeout :表示测试方法运行如果超过了指定时间将会返回错误
  • @Nested :嵌套嵌套测试
  • @ExtendWith :为测试类或测试方法提供扩展类引用

Junit5单元测试示例

import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvFileSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.platform.commons.util.StringUtils;

import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Iterator;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;

class Junit5Tests {

    @Test
    @DisplayName("第一次测试")
    public void firstTest() {
        System.out.println("hello world");
    }

    /*
    新的断言
        在断言 API 设计上,JUnit 5 进行显著地改进,并且充分利用 Java 8 的新特性,特别是 Lambda 表达式,最终提供了新的断言类: org.junit.jupiter.api.Assertions 。
        许多断言方法接受 Lambda 表达式参数,在断言消息使用 Lambda 表达式的一个优点就是它是延迟计算的,如果消息构造开销很大,这样做一定程度上可以节省时间和资源。
     */

    @Test
    void testGroupAssertions() {
        int[] numbers = {0, 1, 2, 3, 4};
        Assertions.assertAll("numbers",
                () -> Assertions.assertEquals(numbers[1], 1),
                () -> Assertions.assertEquals(numbers[3], 3),
                () -> Assertions.assertEquals(numbers[4], 4)
        );
    }

    @Test
    @DisplayName("异常测试")
    public void exceptionTest() {
        ArithmeticException exception = Assertions.assertThrows(
                //扔出断言异常
                ArithmeticException.class, () -> System.out.println(1 % 0));

    }

    @Test
    @DisplayName("超时测试")
    public void timeoutTest() {
        //如果测试方法时间超过1s将会异常
        Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
    }

    @Test
    @DisplayName("超时方法测试")
    void test_should_complete_in_one_second() {
        Assertions.assertTimeoutPreemptively(Duration.of(1, ChronoUnit.SECONDS), () -> Thread.sleep(2000));
    }

    @Test
    @DisplayName("测试捕获的异常")
    void assertThrowsException() {
        String str = null;
        Assertions.assertThrows(IllegalArgumentException.class, () -> {
            Integer.valueOf(str);
        });
    }

    /*
    参数化测试
        @ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
        @NullSource: 表示为参数化测试提供一个null的入参
        @EnumSource: 表示为参数化测试提供一个枚举入参
        @CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
        @MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
        实现org.junit.jupiter.params.provider.ArgumentsProvider接口,任何外部文件都可以作为它的入参
     */

    @ParameterizedTest
    @ValueSource(strings = {"one", "two", "three"})
    @DisplayName("参数化测试1")
    public void parameterizedTest1(String string) {
        System.out.println(string);
        assertTrue(StringUtils.isNotBlank(string));
    }

    /**
     * csv文件内容,格式:学生,年龄
     */
    @ParameterizedTest
    @CsvFileSource(resources = "/test.CSV")  //指定csv文件位置
    @DisplayName("参数化测试-csv文件")
    public void parameterizedTest2(String name, Integer age) {
        System.out.println("name:" + name + ",age:" + age);
        Assertions.assertNotNull(name);
        Assertions.assertNotNull(age);
    }

    @ParameterizedTest
    @MethodSource("method")    //指定方法名
    @DisplayName("方法来源参数")
    public void testWithExplicitLocalMethodSource(String name) {
        System.out.println(name);
        Assertions.assertNotNull(name);
    }

    static Stream<String> method() {
        return Stream.of("apple", "banana");
    }

    /*
    内嵌单元测试
        @Nested 注解,能够以静态内部成员类的形式对测试用例类进行逻辑分组
     */

    static class Nested1 {
        @BeforeEach
        void init() {
            System.out.println("Nested1_init");
        }

        @Test
        @DisplayName("Nested")
        void isInstantiatedWithNew() {
            System.out.println("最一层--内嵌单元测试");
        }

        @Nested
        @DisplayName("Nested2")
        class Nested2 {

            @BeforeEach
            void Nested2_init() {
                System.out.println("Nested2_init");
            }

            @Test
            void Nested2_test() {
                System.out.println("第二层-内嵌单元测试");
            }

            @Nested
            @DisplayName("Nested3")
            class Nested3 {

                @BeforeEach
                void Nested3_init() {
                    System.out.println("Nested3_init");
                }

                @Test
                void Nested3_test() {
                    System.out.println("第三层-内嵌单元测试");
                }
            }
        }

    }

    @RepeatedTest(10) //表示重复执行10次
    @DisplayName("重复测试")
    public void testRepeated() {
        assertTrue(1 == 1);
    }

    @TestFactory
    @DisplayName("动态测试")
    Iterator<DynamicTest> dynamicTests() {
        return Arrays.asList(
                dynamicTest("第一个动态测试", () -> assertTrue(true)),
                dynamicTest("第二个动态测试", () -> assertEquals(4, 2 * 2))
        ).iterator();
    }
}

Junit5单元测试执行顺序

通过org.junit.jupiter.api.TestMethodOrder可以指定对应的org.junit.jupiter.api.MethodOrderer排序策略

MethodOrderer提供了三种策略:

  • Alphanumeric:根据测试方法的名称和形式参数列表对它们进行字母数字排序。
  • OrderAnnotation:根据通过注释指定的值对测试方法进行数字排序org.junit.jupiter.api.Order
  • Random:伪随机地执行测试方法,并支持自定义随机策略的配置。
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;

//@TestMethodOrder(MethodOrderer.Alphanumeric.class)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
//@TestMethodOrder(MethodOrderer.Random.class)
class OrderedTestsDemo {

    @Test
    @Order(1)
    void nullValues() {
        // perform assertions against null values
    }

    @Test
    @Order(2)
    void emptyValues() {
        // perform assertions against empty values
    }

    @Test
    @Order(3)
    void validValues() {
        // perform assertions against valid values
    }

}

Spring Boot中使用单元测试

引入测试依赖

Junit的测试有局限性,无法直接测试启动后Spring环境的功能,借助Spring提供的spring-boot-starter-test依赖,可以便捷的调试Spring容器中Bean的功能。

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

创建一个标准的Spring Boot的测试单元

@SpringBootTest这个注解所在的文件一定要和注解@SpringBootApplication在同个层级的文件目录下。

@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {

}

事务回滚

@Transactional:

@Transactional注解修饰的类或方法,可以自动完成事务回滚。

@Rollback

Spring Boot中使用@Transactional默认回滚事务,而使用@Rollback可以阻止事务自动回滚。

@Rollback(true)默认值是true,表示事务会自动回滚,若是想不回滚事务,将值设置为false即可:@Rollback(false)

测试环境配置

@TestConfiguration:

@TestConfiguration是Spring Boot Test提供的一种工具,用它我们可以在一般的@Configuration之外补充测试专门用的Bean或者自定义的配置。

@TestConfiguration能够:

  • 补充额外的Bean
  • 覆盖已存在的Bean

@ContextConfiguration:

在 Spring Boot 测试中引入多个配置。

引入配置文件:

@ContextConfiguration(Locations="classpath:applicationContext.xml")

引入配置类型:

@ContextConfiguration(classes = SimpleConfiguration.class)

打包测试

junit提供了测试套件,就是可以运行一个测试类使得一个或一些测试类被junit测试运行。

方式:在打包测试类上使用@RunWith标注传递一个参数 Suite.class 。同时,我们还需要另外一个标注 @Suite.SuiteClasses ,来表明这个类是一个打包测试类。

import org.junit.runner.RunWith;
import org.junit.runners.Suite;

/**
 * 打包测试
 */
@RunWith(Suite.class)
@Suite.SuiteClasses({
        Junit4Test.class,
        TestingApplicationTests.class
})
public class AllTests {
}

Mock测试,在这个模拟环境中测试web端点

Mock测试使用的是Mockito框架,在测试的执行过程中使用Mockito的mock模拟底层数据的返回,达到测试web程序的目的。

注入MockMvc实例

注入mock测试实例有两种方式。

方式一:注入 web 程序上下文,初始化MockMvc实例。

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootTest
public class MockMvcExampleTests2 {

    @Autowired
    private WebApplicationContext webApplicationContext;
    private MockMvc mvc;

    @Before
    public void setUp() throws Exception {
        mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }

    @Test
    public void exampleTest() throws Exception {
        MvcResult mvcResult = this.mvc.perform(get("/"))
                //期望响应状态正常
                .andExpect(status().isOk())
                //期望响应的信息为:Hello,World!
                .andExpect(content().string("Hello,World!"))
                .andReturn();
        System.out.println(mvcResult.getResponse().getContentAsString());
    }

}

方式二:在 Spring Boot 测试类上添加注解@AutoConfigureMockMvc

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class MockMvcExampleTests {

    @Autowired
    private MockMvc mvc;

    @Test
    public void exampleTest() throws Exception {
        MvcResult mvcResult = this.mvc.perform(get("/"))
                //期望响应状态正常
                .andExpect(status().isOk())
                //期望响应的信息为:Hello,World!
                .andExpect(content().string("Hello,World!"))
                .andReturn();
        System.out.println(mvcResult.getResponse().getContentAsString());
    }

}
使用@MockBean模拟相应对象

@MockBean主要作用是:使用mock对象代替原来spring的bean,然后模拟底层数据的返回,而不是调用原本真正的实现。

示例:这里接口返回的数据已经不是原本repository查询到的数据,而是mock模拟的对象返回的数据。

import com.example.testing.entity.User;
import com.example.testing.repository.UserRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;

import java.util.ArrayList;
import java.util.List;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class MockMvcExampleTests {

    @Autowired
    private MockMvc mvc;
    /**
     * 使用@MockBean模拟相应对象
     */
    @MockBean
    private UserRepository userRepository;

    @Test
    public void exampleTest() throws Exception {
        List<User> users = new ArrayList<>();
        User user = new User();
        user.setId(1L);
        user.setUsername("Github");
        user.setPassword("123456");
        users.add(user);
        Mockito.when(userRepository.findAll()).thenReturn(users);

        MvcResult mvcResult = this.mvc.perform(get("/user/query/all"))
                .andExpect(status().isOk())
                .andDo(MockMvcResultHandlers.print())
                .andExpect(content().string(mapper.writeValueAsString(users)))
                .andReturn();
        System.out.println(mvcResult.getResponse().getContentAsString());
    }

}

参考文档

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值