我的编程空间,编程开发者的网络收藏夹
学习永远不晚

springboot集成测试容器重启问题的处理

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

springboot集成测试容器重启问题的处理

背景

spring boot test的项目中常用的测试框架, 最近在写集成测试的时候发现一个比较奇怪的问题,当我在运行多个测试用例的时候会偶尔重新启动整个容器上下文,由于后期业务逐渐复杂,大量的测试用例需要运行,这个问题直接导致回归测试的效率降低。

在这里插入图片描述

举个例子:

在这里插入图片描述

几个类:


@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestApplication.class)
public class BaseApiTest {
    @Test
    public void init() {
    }
}
public class ApiTest1 extends BaseApiTest {
    @MockBean
    private Service service;
    @Test
    public void test1() {
        service.call();
    }
}
public class ApiTest2 extends BaseApiTest {
    @Autowired
    private Service service;
    @Test
    public void test2() {
        service.call();
    }
}
@SpringBootApplication
@Slf4j
public class TestApplication {
    public static void main(String[] args) {
        log.info("启动容器");
        new SpringApplication(TestApplication.class).run(args);
    }
}
@Component
public class Service {
    public void call() {
        System.out.println("service called");
    }
}

运行test包下所有测试:

在这里插入图片描述

发现容器重复启动了。

测试用例的运行流程

可以开启idea的线程堆栈跟踪,观察整个容器的启动路径

在这里插入图片描述

com.intellij.rt.junit是idea内部的实现,点击idea的运行单测会触发JunitStarter的main函数去启动,可以去GitHub找到源码:

在这里插入图片描述

做一些准备工作找到指定的runner就开始调用junit的包去执行编写的单测,junit为了灵活的扩展不同的测试运行环境,类似SPI机制动态获取Runner去运行单测。例如我的例子里指定了SpringRunner就是需要依赖Spring容器的一个实现,这样就让测试用例可以运行在Spring环境中。

在这里插入图片描述

junit的入口也支持在测例前后去插入一些操作,自己去实现RunnerListener即可。junit默认实现了监听器去记录测例的耗时,失败的数量等信息。

在这里插入图片描述

我指定的Runner是SpringRunner,其与SpringJUnit4ClassRunner并没什么区别,可以看其实现完全继承了SpringJUnit4ClassRunner的实现。

所以我们直接看SpringJUnit4ClassRunner的runner实现:

在这里插入图片描述

它首先判断了当前的环境是否需要忽略单测。如果忽略会在通知里得到通知。关于环境的指定控制可以参考注解@IfProfileValue,判断环境正确之后继续调用junit包父类ParentRunner的方法执行。

其定义了执行的基本的模板:

在这里插入图片描述

classBlock里面定义线程池执行和测例执行的一些before和after逻辑,里面的runChild是抽象方法,也是留给各个Runner实现的钩子。getFilteredChildren能够根据@Test注解拿到所有需要运行的用例方法,然后每个方法去调用具体的Runner运行。

在这里插入图片描述

其流程图如下

在这里插入图片描述

在这里插入图片描述

SpringJUnit4ClassRunner的运行每个方法会给每个测例方法进行一个封装成Statement。关键就在methodBlock方法,它实现了Spring boot对方法运行的封装

在这里插入图片描述

createTest会在测试的上下文里维护一个配置,然后会用通知机制一样去依次调用需要准备的东西,其中就包含spring容器的上下文。

在这里插入图片描述

在这里插入图片描述

其会执行injectDependencies,处理依赖的bean准备。TestContext是每个单测方法需要运行的上下文,在Spring boot的测试环境下,其维护了Spring的上下文,每个方法的执行都会去获取Spring的上下文

在这里插入图片描述

根据单测的相关信息文获取上spring的上下文,为了避免每次都去加载容器,TestContext会维护一个spring容器的缓存,CacheAwareContextLoaderDelegate

在这里插入图片描述

在这里插入图片描述

CacheAwareContextLoaderDelegate其内部的获取又是通过单测配置信息去ContextCache获取的,其内部是一个同步SynchronizedMap去保存的。

在这里插入图片描述

其内部实现看测例的配置信息去获取加载过的容器,如果没获取到就会触发重新加载新容器的流程,所以关键就是看key在Map中的获取逻辑,其底层是spring test自己实现一个的Map

在这里插入图片描述

可以看到其是基于HashMap的一个哈希结构,根据Jdk的源码,我们可以知道HashMap的key是根据hashCode与equals去比较key,那可以确定,要想复用同一个容器就得看Key值的hashCode和equals实现。接下来我们看MergedContextConfiguration源码.

在这里插入图片描述

在这里插入图片描述

可以发现其比较的值是:


 	 * @param testClass the test class for which the configuration was merged
	 * @param locations the merged context resource locations
	 * @param classes the merged annotated classes
	 * @param contextInitializerClasses the merged context initializer classes
	 * @param activeProfiles the merged active bean definition profiles
	 * @param propertySourceLocations the merged {@code PropertySource} locations
	 * @param propertySourceProperties the merged {@code PropertySource} properties
	 * @param contextCustomizers the context customizers
	 * @param contextLoader the resolved {@code ContextLoader}
	 * @param cacheAwareContextLoaderDelegate a cache-aware context loader
	 * delegate with which to retrieve the parent context
	 * @param parent the parent configuration or {@code null} if there is no parent

这些参数确定了能否共享SpringApplication,那两个测试类一个@Autowired,另一个使用@MockBean,肯定是改变这里面某个值,我们可以回溯这个MergedContextConfiguration是在什么时候被初始化的。这个还要追溯到idea的启动类,找到Runner的时候,SpringJUnit4ClassRunner的初始化的过程。

在每个测试类的运行都会唤起SpringJUnit4ClassRunner初始化,调用构造函数的时候会去加载测试类的上下文

Texrt

去创建这个TextContextManager

在这里插入图片描述

这里首先会根据被测试类的继承关系和注解的递归去找到固定包下面被注解@BootstrapWith修饰的类,因为是Spring boot test这里会根据@SpringBootTest 注解找到SpringBootTestContextBootstrapper类,找到这个引导类之后就会去初始化MergedContextConfiguration了。

在这里插入图片描述

引导类通过SPI机制加载到所有的Customizer,并根据需要DefinitionsParser,进行转换,保存在MergedContextConfiguration的一个字段,mock的一个属性会在转换的时候记录到,而非mock的contextCustomizers则不会记录。

注意这里提到的

在这里插入图片描述

在这里插入图片描述

两个类一个用mock的字段,一个用非mock的字段,两个MockitoContextCustomizer的definitions就不一样,因此无法共享上下文,因此需要重新启动一个Spring容器,并存放到CacheAwareContextLoaderDelegate,以便后面共享。

结论

分析源码的设计,发现应用了很多SPI与可扩展的设计,idea与junit的解耦,junit的抽象与模板定义与各个测试框架的扩展。

针对容器重启的角度,对于一个类来说,一定是共享一个spring上下文,但是不同的类可能由于注入的bean的方式不同导致无法共享spring上下文,所以导致重启会浪费掉一些时间,因此建议确定好mock的边界,对尽量多的测例共享一个容器视角可以提高单测效率,基于此可以设计多继承关系的单测结构,并把注入的bean向上共享,避免各个测试子类自己去注入出现不一致的情况。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

springboot集成测试容器重启问题的处理

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

k8s集群部署时etcd容器不停重启问题以及处理详解

一次在k8s集群中创建实例发现etcd集群状态出现连接失败状况,导致创建实例失败,下面这篇文章主要给大家介绍了关于k8s集群部署时etcd容器不停重启问题以及处理的相关资料,需要的朋友可以参考下
2023-01-03

在Go语言中如何解决并发网络请求的请求错误重试和容错处理问题?

在Go语言中如何解决并发网络请求的请求错误重试和容错处理问题?随着互联网的高速发展,网络请求已经成为了日常开发中不可或缺的一部分。然而,网络请求并不总是成功的,可能会遇到各种错误。在并发请求中,这些错误很可能会导致整个程序的崩溃,因此我们需
2023-10-22

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录