工程升级SpringBoot之后,突然发现之前写的几个简单的单元测试类无法正常执行了,因为SpringBoot工程的配置方式与之前还是有比较大的差异。
而且之前直接使用Junit来写单元测试,这一次打算直接升级到SpringBoot的Test方式。
1、引入依赖包
之前是直接引用junit依赖包,需要更改为spring-boot-starter-test(里面包含了对junit的依赖)
org.springframework.boot spring-boot-starter-testtest
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/10772https://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.htmlhttps://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-testtest org.junit.vintage junit-vintage-engine
改完之后发现很多测试类报错了,原来之前写的很多类都是用的Junit4的Test注解。逐个类进行处理,到时没有啥特别的。
需要注意的是,对于Junit5,通过SpringBootTest注解,已经不再需要@RunWith注解了。
而对于部分不需要SpringBootTest的情况,@RunWith注解也发生了变化,需要改成:@ExtendWith(SpringExtension.class)即可。其他变动都是无感的。