• 聊聊SpringBoot单元测试


    之前把我的mall项目升级到Spring Boot 2.7的时候,很多之前的测试方法都不能用了,原来是Spring Boot Test已经升级支持JUnit 5了。今天我们来聊聊新版Spring Boot Test的使用,有了它,我们就不需要再使用main方法来测试了!

    JUnit 简介

    JUnit是一款Java语言的单元测试框架,目前大多数Java开发环境都已经支持它了。JUnit测试也就是所谓的白盒测试,在程序员知道程序内部逻辑的基础上进行的测试,使用JUnit能让我们快速地完成单元测试。Spring Boot Test将JUnit和其他测试框架结合起来,提供了便捷高效的测试手段,目前Spring Boot 2.7版本采用的是JUnit 5。

    常用注解

    在使用Spring Boot Test之前,我们先来了解下它常用的注解,这对使用它很有帮助,具体参考下表即可!

    注解作用
    @SpringBootTest用于指定测试类启用Spring Boot Test,默认会提供Mock环境
    @ExtendWith如果只想启用Spring环境进行简单测试,不想启用Spring Boot环境,可以配置扩展为:SpringExtension
    @Test指定方法为测试方法
    @TestMethodOrder用于配置测试类中方法的执行顺序策略,配置为OrderAnnotation时,按@Order顺序执行
    @Order用于配置方法的执行顺序,数字越低执行顺序越高
    @DisplayName用于指定测试类和测试方法的别名
    @BeforeAll在测试类的所有测试方法前执行一次,可用于全局初始化
    @AfterAll在测试类的所有测试方法后执行一次,可用于全局销毁资源
    @BeforeEach在测试类的每个测试方法前都执行一次
    @AfterEach在测试类的每个测试方法后都执行一次
    @Disabled禁用测试方法
    @RepeatedTest指定测试方法重复执行
    @ParameterizedTest指定参数化测试方法,类似重复执行,从@ValueSource中获取参数
    @ValueSource用于参数化测试指定参数
    @AutoConfigureMockMvc启用MockMvc的自动配置,可用于测试接口

    基本使用

    下面我们来聊聊这些注解的基本使用,通过它们可以实现一些基本的单元测试。

    集成Spring Boot Test

    如果你想在项目中集成Spring Boot Test的话,需要先在pom.xml中添加如下依赖。

    1. <dependency>
    2.     <groupId>org.springframework.boot</groupId>
    3.     <artifactId>spring-boot-starter-test</artifactId>
    4.     <scope>test</scope>
    5. </dependency>

    最简单的测试

    • 我们先来一个最简单的单元测试,使用@SpringBootTest注解启用单元测试,使用@Test指定测试方法,使用Assertions类的方法来断言结果是否符合预期,具体代码如下。

    1. /**
    2.  * JUnit基本测试
    3.  * Created by macro on 2022/10/11.
    4.  */
    5. @SpringBootTest
    6. public class FirstTest {
    7.     @Test
    8.     public void test() {
    9.         int a=1;
    10.         Assertions.assertEquals(1,a);
    11.     }
    12. }
    • 然后点击测试方法左侧按钮即可进行测试。

    • 执行完成后我们在IDEA的执行窗口中就可以看到方法测试通过了,由于使用@SpringBootTest启用了Spring Boot环境,日志中会输出Spring Boot的banner。

    指定测试方法顺序

    • 我们可以通过@TestMethodOrder注解和@Order注解来指定所有测试方法的执行顺序,具体代码如下。

    1. /**
    2.  * JUnit指定方法测试顺序
    3.  * Created by macro on 2022/10/10.
    4.  */
    5. @ExtendWith(SpringExtension.class)
    6. @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
    7. public class MethodOrderTest {
    8.     private static final Logger LOGGER = LoggerFactory.getLogger(MethodOrderTest.class);
    9.     @Test
    10.     @Order(1)
    11.     @DisplayName("order为1的方法")
    12.     void lowOrder(){
    13.         LOGGER.info("lowOrder method");
    14.     }
    15.     @Test
    16.     @Order(10)
    17.     @DisplayName("order为10的方法")
    18.     void highOrder(){
    19.         LOGGER.info("highOrder method");
    20.     }
    21. }
    • 点击类左侧测试按钮,可以直接运行该类中的所有测试方法。

    • 这里由于我们使用了@DisplayName注解给测试方法取了个别名,而且我们使用了@ExtendWith指定了运行环境为Spring而不是Spring Boot,所以日志中不会出现Spring Boot的banner,执行速度也更快。

    生命周期测试

    • 我们还可以通过JUnit 5的生命周期注解来执行测试方法,比如在@BeforeAll注解指定的方法中做全局初始化,在@AfterAll注解指定的方法中做资源的销毁,具体代码如下。

    1. /**
    2.  * JUnit生命周期测试
    3.  * Created by macro on 2022/10/10.
    4.  */
    5. @ExtendWith(SpringExtension.class)
    6. public class LifecycleTest {
    7.     private static final Logger LOGGER = LoggerFactory.getLogger(LifecycleTest.class);
    8.     @BeforeAll
    9.     static void allInit(){
    10.         LOGGER.info("allInit():在所有方法前执行,只执行一次");
    11.     }
    12.     @BeforeEach
    13.     void eachInit(){
    14.         LOGGER.info("eachInit():在测试方法前执行,每个测试方法前都执行");
    15.     }
    16.     @Test
    17.     void successTest() {
    18.         LOGGER.info("successTest():方法执行成功");
    19.     }
    20.     @AfterEach
    21.     void eachDown(){
    22.         LOGGER.info("eachDown():在测试方法后执行,每个测试方法后都执行");
    23.     }
    24.     @AfterAll
    25.     static void allDown(){
    26.         LOGGER.info("allDown():在测试方法后执行,每个测试方法后都执行");
    27.     }
    28. }
    • 测试完成后,控制台输出日志如下。

    断言的使用

    我们可以通过Assertions类中提供的断言API来断言测试结果。

    • 例如我们可以使用fail方法直接断言方法执行失败并输出提示信息。

    1. /**
    2.  * JUnit断言测试
    3.  * Created by macro on 2022/10/11.
    4.  */
    5. @ExtendWith(SpringExtension.class)
    6. public class AssertTest {
    7.     @Test
    8.     void failTest() {
    9.         Assertions.fail("failTest():方法执行失败");
    10.     }
    11. }
    • 测试方法执行后会直接抛出异常信息。

    • 还可以通过assertTrueassertNullassertEquals这类方法来断言结果是否符合预期。

    1. /**
    2.  * JUnit断言测试
    3.  * Created by macro on 2022/10/11.
    4.  */
    5. @ExtendWith(SpringExtension.class)
    6. public class AssertTest {
    7.     @Test
    8.     void failTest() {
    9.         Assertions.fail("failTest():方法执行失败");
    10.     }
    11.     @Test
    12.     void trueTest(){
    13.         Assertions.assertTrue(1==1);
    14.     }
    15.     @Test
    16.     void trueFalse(){
    17.         Assertions.assertFalse(3<=2);
    18.     }
    19.     @Test
    20.     void nullTest(){
    21.         String str = null;
    22.         Assertions.assertNull(str);
    23.     }
    24.     @Test
    25.     void notNullTest(){
    26.         String str = "test";
    27.         Assertions.assertNotNull(str);
    28.     }
    29.     @Test
    30.     void equalsTest(){
    31.         String str1 = "test";
    32.         String str2 = "test";
    33.         Assertions.assertEquals(str1,str2);
    34.     }
    35.     @Test
    36.     void notEqualsTest(){
    37.         String str1 = "test";
    38.         String str2 = "test";
    39.         Assertions.assertNotEquals(str1,str2);
    40.     }
    41. }
    • 也可以使用assertThrows方法来断言方法中抛出的异常。

    1. /**
    2.  * JUnit断言测试
    3.  * Created by macro on 2022/10/11.
    4.  */
    5. @ExtendWith(SpringExtension.class)
    6. public class AssertTest {
    7.     private static final Logger LOGGER = LoggerFactory.getLogger(LifecycleTest.class);
    8.     @Test
    9.     void throwsTest(){
    10.         Assertions.assertThrows(NullPointerException.class,()->{
    11.             String str = null;
    12.             LOGGER.info(str.toLowerCase());
    13.         });
    14.     }
    15. }
    • 还可通过assertTimeout方法断言方法的执行时间。

    1. /**
    2.  * JUnit断言测试
    3.  * Created by macro on 2022/10/11.
    4.  */
    5. @ExtendWith(SpringExtension.class)
    6. public class AssertTest {
    7.     @Test
    8.     void timeoutTest(){
    9.         Assertions.assertTimeout(Duration.ofMillis(1000),()->{
    10.             long sleepTime = 2000;
    11.             ThreadUtil.sleep(sleepTime);
    12.             LOGGER.info("timeoutTest():休眠{}毫秒",sleepTime);
    13.         });
    14.     }
    15. }
    • 或者通过assertAll方法将几个断言结合起来使用,Assertions类中提供的工具方法很多,具体可以参考它的代码。

    1. /**
    2.  * JUnit断言测试
    3.  * Created by macro on 2022/10/11.
    4.  */
    5. @ExtendWith(SpringExtension.class)
    6. public class AssertTest {
    7.     @Test
    8.     void assertAllTest(){
    9.         Assertions.assertAll(()->{
    10.             trueTest();
    11.         },()->{
    12.             nullTest();
    13.         },()->{
    14.             equalsTest();
    15.         });
    16.     }
    17. }

    其他测试

    • Spring Boot Test除了上述测试功能,还可以使用@Disabled来禁用某个测试方法。

    1. /**
    2.  * JUnit其他测试
    3.  * Created by macro on 2022/10/10.
    4.  */
    5. @ExtendWith(SpringExtension.class)
    6. public class OtherTest {
    7.     @Test
    8.     @Disabled("用于测试@Disabled注解")
    9.     void disabledTest() {
    10.         LOGGER.info("disabledTest():方法被执行");
    11.     }
    12. }
    • 也可以使用@RepeatedTest来实现循环测试。

    1. /**
    2.  * JUnit其他测试
    3.  * Created by macro on 2022/10/10.
    4.  */
    5. @ExtendWith(SpringExtension.class)
    6. public class OtherTest {
    7.     private static final Logger LOGGER = LoggerFactory.getLogger(LifecycleTest.class);
    8.     private static int count = 0;
    9.     @RepeatedTest(3)
    10.     void repeatedTest() {
    11.         count++;
    12.         LOGGER.info("repeatedTest():重复执行第{}次",count);
    13.     }
    14. }
    • 还可以通过@ParameterizedTest来进行参数化测试。

    1. /**
    2.  * JUnit其他测试
    3.  * Created by macro on 2022/10/10.
    4.  */
    5. @ExtendWith(SpringExtension.class)
    6. public class OtherTest {
    7.     private static final Logger LOGGER = LoggerFactory.getLogger(LifecycleTest.class);
    8.     @ParameterizedTest
    9.     @ValueSource(ints = {1,2,3})
    10.     public void parameterizedTest(int a){
    11.         LOGGER.info("parameterizedTest():a={}",a);
    12.     }
    13. }
    • 运行以上测试方法后,具体测试结果如下。

    项目实战

    上面介绍了Spring Boot Test的基本使用,下面我们结合项目来使用下它。

    Dao层测试

    如果我们的项目需要对数据访问层Dao中的方法进行测试的话,直接注入Mapper接口,在测试方法中直接调用即可,这里对根据ID查询品牌的Mapper方法进行测试。

    1. /**
    2.  * Dao层方法测试
    3.  * Created by macro on 2022/10/11.
    4.  */
    5. @SpringBootTest
    6. public class MapperTest {
    7.     private static final Logger LOGGER = LoggerFactory.getLogger(MapperTest.class);
    8.     @Autowired
    9.     private PmsBrandMapper brandMapper;
    10.     @Test
    11.     void testGetById(){
    12.         long id = 6;
    13.         PmsBrand pmsBrand = brandMapper.selectByPrimaryKey(id);
    14.         LOGGER.info("brand name:{}",pmsBrand.getName());
    15.         Assertions.assertEquals("小米",pmsBrand.getName());
    16.     }
    17. }

    Service层测试

    对业务层Service中的方法测试也是一样的,直接注入Service接口,在测试方法中直接调用即可,这里对根据ID查询品牌的Service方法进行测试。

    1. /**
    2.  * Service层方法测试
    3.  * Created by macro on 2022/10/11.
    4.  */
    5. @SpringBootTest
    6. public class ServiceTest {
    7.     private static final Logger LOGGER = LoggerFactory.getLogger(ServiceTest.class);
    8.     @Autowired
    9.     private PmsBrandService brandService;
    10.     @Test
    11.     void testGetById(){
    12.         long id = 6;
    13.         PmsBrand pmsBrand = brandService.getBrand(id);
    14.         LOGGER.info("brand name:{}",pmsBrand.getName());
    15.         Assertions.assertEquals("小米",pmsBrand.getName());
    16.     }
    17. }

    Controller层测试

    对于Controller层方法进行测试,有时我们需要模拟请求,使用MockMvc即可,这里模拟测试下分页查询品牌列表的接口。

    1. /**
    2.  * Controller层方法测试
    3.  * Created by macro on 2022/10/11.
    4.  */
    5. @SpringBootTest
    6. @AutoConfigureMockMvc
    7. public class ControllerTest {
    8.     @Autowired
    9.     private MockMvc mockMvc;
    10.     @Test
    11.     void mvcTest() throws Exception{
    12.         //模拟发送一个请求访问分页查询品牌列表的接口
    13.         mockMvc.perform(MockMvcRequestBuilders.get("/brand/list"//设置请求地址
    14.                 .param("pageNum","1"//设置请求参数
    15.                 .param("pageSize","5"))
    16.                 .andExpect(MockMvcResultMatchers.status().isOk()) //断言返回状态码为200
    17.                 .andDo(MockMvcResultHandlers.print()) //在控制台打印日志
    18.                 .andReturn(); //返回请求结果
    19.     }
    20. }

    由于我们选择了在控制台输出日志,控制台将输出如下信息。

    总结

    今天带大家体验了一把Spring Boot Test,作为Spring Boot官方测试框架,确实功能很强大。由于其主要基于JUnit 5,和JUnit 5的用法基本一致。使用它进行单元测试,无需启动整个项目,更快更好用!

  • 相关阅读:
    全量知识系统 程序详细设计 库模式的存储库模型: “三生”(派生衍生自生) (Q&A SmartChat)
    【Java】Java核心API概述
    DDR电源硬件设计要点
    Go学习第十二章——Go反射与TCP编程
    防火墙的技术(NAT NAT-Server 策略路由 ) 第二十课
    2025汤家凤考研数学,基础视频课程+百度网盘+PDF真题讲解
    快捷方式变白解决方法
    操作系统(一)
    Retrofit java.security.cert.CertPathValidatorException:
    嵌入式Qt-实现两个窗口的切换
  • 原文地址:https://blog.csdn.net/m0_71777195/article/details/127441312