SpringBoot下进行单元测试
作者:mmseoamin日期:2023-12-21

工程升级SpringBoot之后,突然发现之前写的几个简单的单元测试类无法正常执行了,因为SpringBoot工程的配置方式与之前还是有比较大的差异。

而且之前直接使用Junit来写单元测试,这一次打算直接升级到SpringBoot的Test方式。

1、引入依赖包

之前是直接引用junit依赖包,需要更改为spring-boot-starter-test(里面包含了对junit的依赖)


    org.springframework.boot
    spring-boot-starter-test
    test

2、对于需要创建Unit Test的类/方法,直接在IDEA里右键->go to -> Test

IDEA会自动创建对应的Test类,然后编写对应的测试代码即可。

需要注意的是,SpringBoot有默认的约定,测试类需要与被测试类在相同的package下(目录结构一个是main,一个是test,后面的目录结构都是一样的)

在创建好的类上需要加上Test相关的注解即可:

@SpringBootTest

@RunWith(SpringRunner.class)

方法上加上@Test注解(与Junit一样的)

建议创建一个基类,然后将注解直接放在基类上,这样其他的测试类可以直接extends该基类即可。

3、由于工程配置文件里面有敏感信息,故配置文件都不在工程默认目录下,而是通过启动参数指定的配置文件

此时,对于Test类,就需要通过参数进行指定配置文件,来覆盖默认的配置。

@SpringBootTest
@RunWith(SpringRunner.class)
@TestPropertySource(
locations = { "file:XXXXX\\application-dev.properties", "file:XXXXX\\bootstrap-dev.properties" }, properties = "spring.cloud.nacos.discovery.password=YYYYYYY")

需要注意的是,SpringBoot的TestPropertySource是不支持yml文件格式的,目前只能通过properties文件来进行配置。(个别参数可以直接通过properties参数进行指定)

https://github.com/spring-projects/spring-boot/issues/10772icon-default.png?t=N5K3https://github.com/spring-projects/spring-boot/issues/10772

试了很多网上的方法都没有成功(Spring @PropertySource using YAML - Stack Overflow),最后看到SpringBootTest注解上支持args参数,可以通过args指定启动参数,这样就可以通过spring.config.location来指定yml配置文件了。

需要注意,如果args有多个参数的时候,解析好像有BUG,无法正常处理,比如下面的配置,如果将nacos的password也放在args里面,会导致password错误。

@SpringBootTest( args = "--spring.cloud.nacos.discovery.password=YYYYYY --spring.config.location=classpath:/,XXXXXX\\application-dev.yml,XXXXXX\\bootstrap-dev.yml")

需要改成如下形式才可以:

@SpringBootTest(properties = "spring.cloud.nacos.discovery.password=YYYYYYY",

args = "--spring.config.location=classpath:/,XXXXXX\\application-dev.yml,XXXXXX\\bootstrap-dev.yml")

4、前面提到SpringBoot有默认的约定,自动生成的测试类会在相同的package下,但是如果我需要自定义一些测试类,与main下面的类或者方法没有关联。此时自定义的类放在我们自定义的一个package里面。

如果此时直接继承上面的基类,则会出现:Unable to find a @SpringBootConfiguration,异常。

其实,主要是SpringBootTest在启动时,默认进行了特定目录的扫描,找到启动类:SpringBootApplication注解标注的类。

而此时自定义的类在test目录下的package里面,与main不再同一个目录,故无法扫描到启动类。

此时,只需要增加一个参数指定一下启动类即可。(springboot单元测试Unable to find a @SpringBootConfiguration的两种解决方法以及原理 - 简书springboot单元测试大部分情况很简单,只用增加2个注解就行: 注意是大部分情况,因为springboot约定大于配置,如果你不按它的约定,就会出现下面的错误Unabl...https://www.jianshu.com/p/1b80e5c4bb02)

@SpringBootTest(classes = TaApplication.class)

5、SpringBootTest会加载整个ApplicationContext,所以可以在Test类里直接使用@Autowired来注入其他的bean。

但是会有两种特殊情况需要处理,一种是只需要简单测试某一个类,不希望加载所有的context(会比较耗时);另一种是在test目录下定义了一个bean对象,但是在默认的context下不会扫描test下的bean,导致autowired时候无法找到该bean。

此时也有两种方式来处理:

1> 定义一个configuration类,然后指定需要扫描的类或者package,然后在SpringBootTest上指定需要启动的类为该configuration类即可。

@Configuration
@ComponentScan("com.spring.temp")
public class TestConfig {
}
@SpringBootTest(classes = TestConfig.class)
public class TimeLogTest extends BaseTest {……}

说明,因为有时候需要测试的类依赖比较多,需要全部加载context,但是又依赖一个test目录下的bean,此时也可以通过此方式来解决。

将configuration类的扫描范围扩大

@Configuration
@ComponentScan("com.spring.*")
public class TestConfig {
}

然后,在测试类上指定TestConfig类为启动类,通过properties和args指定相应参数即可。

@SpringBootTest(classes = TestConfig.class,
properties = "spring.cloud.nacos.discovery.password=YYYYYY",
args = "--spring.config.location=classpath:/,XXXXXX\\application-dev.yml,XXXXXX\\bootstrap-dev.yml")
public class TimeLogTest extends BaseTest {……}

2> 不使用SpringBootTest来加载,通过TestConfiguration注解来配置(其实与SpringBootTest指定configuration类是一样的原理)

@RunWith(SpringRunner.class)
public class TimeLogTest2 {
    @TestConfiguration
    static class TestConfig {
        @Bean
        public MethodTimeLogService methodTimeLogService() {
        return new MethodTimeLogService();
    }
}
@Autowired
private MethodTimeLogService methodTimeLogService;
@Test
public void test() {
    methodTimeLogService.logTest();
    System.out.println(methodTimeLogService.logTest2());
    System.out.println(methodTimeLogService.logTest3("333333", new String[] { "value2",             "value22" }));
    }
}

Testing in Spring Boot | BaeldungLearn about how the Spring Boot supports testing, to write unit tests efficiently.https://www.baeldung.com/spring-boot-testing

Configuring base package for component scan in Spring boot test - Stack Overflowhttps://stackoverflow.com/questions/48747421/configuring-base-package-for-component-scan-in-spring-boot-test

6、关于日志xml文件

为了方便对不同环境进行不同的日志配置(比如日志级别,日志文件路径等),在log4j2的XML里使用了变量引用。(https://logging.apache.org/log4j/2.x/log4j-spring-boot/index.htmlicon-default.png?t=N5K3https://logging.apache.org/log4j/2.x/log4j-spring-boot/index.html)

在XML配置文件里面,可以直接引用spring的变量配置,于是能够根据工程配置文件(如application.yml)里面的配置对log进行动态配置。

但是在unit test下,一直无法正常获取到变量,应该是springboot test没有对这种情况进行支持。尝试了很多方法,最后都无效。

考虑到unit test对于日志需求不多,只需要考虑简单输出即可。于是在test目录下创建resources目录,然后创建一个log4j2-spring.xml文件,此时spring test会自动使用该配置文件。然后该配置文件里面去除掉spring变量的引用即可。

7、在写一个测试类的时候,添加了@Test注解,但是IDEA却一直提示无法执行该测试方法(没有执行的小图标)。看了一会才发现,原来是用错了@Test注解类。

正常应该用org.junit.jupiter.api.Test类,而刚才使用了org.junit.Test。修改了一下,一切正常。于是好奇为啥Junit会提供两个Test注解类。查看官方文档,才发现,原来是因为一个是Junit4,一个是Junit5的。继续查看文档,才发现SpringBoot Test包为了兼容Junit4,自动添加了对vintage引擎(Junit4版本的引擎,Junit5的引擎是jupiter)的依赖(2.4.0以后的版本将不再默认添加vintage引擎),同时看到官方文档里面有默认推荐配置,需要将vintage引擎依赖包排除。于是修改pom配置:


    org.springframework.boot
    spring-boot-starter-test
    test
    
        
        org.junit.vintage
        junit-vintage-engine
        
    

改完之后发现很多测试类报错了,原来之前写的很多类都是用的Junit4的Test注解。逐个类进行处理,到时没有啥特别的。

需要注意的是,对于Junit5,通过SpringBootTest注解,已经不再需要@RunWith注解了。

而对于部分不需要SpringBootTest的情况,@RunWith注解也发生了变化,需要改成:@ExtendWith(SpringExtension.class)即可。其他变动都是无感的。