单元测试重复启动spring容器问题填坑

问题发现

  • 1、由于在压测环境做库存中心压测,所以第一反应时觉得压测的TPS过高造成的数据库连接不够。故找DBA将库存中心的数据库最大连接数从2048调整到3072。再次发版,数据库连接数依旧被打满。
  • 2、当时Spring配置的连接池大小:最大最小连接数都是300,尝试将数据库连接数降低到50,再次发版,数据库连接数飙升到6000
  • 3、重新调整springboot数据源配置,将最小连接降低到1,最终在压测环境发版成功。

问题处理

【干掉@MockBean】

1
2
3
4
5
6
@MockBean
public RocketMqTempalte  rocketMqTempalte;

@MockBean
public UserIntegration  userIntegration;
//...

优化单元测试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Configuration
public class TestConfiguratin
  @Bean
  private InventoryOperationDetailDao inventoryOperationDetailDao(){
    return Mockito.mock(InventoryOperationDetailDao.class);
  }

  @Bean
  private InventoryOperationDetailManage inventoryOperationDetailManage (){
    return Mockito.mock(InventoryOperationDetailManage.class);
  }
  // ...

问题分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Context Caching
Once the TestContext framework loads an ApplicationContext (or WebApplicationContext) for a test, that context is cached and reused for all subsequent tests that declare the same unique context configuration within the same test suite. To understand how caching works, it is important to understand what is meant by unique and test suite.

An ApplicationContext can be uniquely identified by the combination of configuration parameters that is used to load it. Consequently, the unique combination of configuration parameters is used to generate a key under which the context is cached. The TestContext framework uses the following configuration parameters to build the context cache key:

locations (from @ContextConfiguration)

classes (from @ContextConfiguration)

contextInitializerClasses (from @ContextConfiguration)

contextCustomizers (from ContextCustomizerFactory)  this includes @DynamicPropertySource methods as well as various features from Spring Boots testing support such as @MockBean and @SpyBean.

contextLoader (from @ContextConfiguration)

parent (from @ContextHierarchy)

activeProfiles (from @ActiveProfiles)

propertySourceLocations (from @TestPropertySource)

propertySourceProperties (from @TestPropertySource)

resourceBasePath (from @WebAppConfiguration)

For example, if TestClassA specifies {"app-config.xml", "test-config.xml"} for the locations (or value) attribute of @ContextConfiguration, the TestContext framework loads the corresponding ApplicationContext and stores it in a static context cache under a key that is based solely on those locations. So, if TestClassB also defines {"app-config.xml", "test-config.xml"} for its locations (either explicitly or implicitly through inheritance) but does not define @WebAppConfiguration, a different ContextLoader, different active profiles, different context initializers, different test property sources, or a different parent context, then the same ApplicationContext is shared by both test classes. This means that the setup cost for loading an application context is incurred only once (per test suite), and subsequent test execution is much faster.

MockitoContextCustomizerFactory源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// ...
class MockitoContextCustomizerFactory implements ContextCustomizerFactory{
    @overried
    public MockitoContextCustomizerFactory createContextCustomizer(Class<?> testClass,List<ContextConfigurationAttributes> configAttributes){
        // we gather the explicit mock definitions here since they from part of the
        // MergedContextConfiguration key. Different mocks need to hava a fifferent key . !!! 看这里 !!!
        DefinitionsParser parser = new DefinitionsParser();
        parser.parse(testClass);
        return new MockitoContextCustomizer(parser.getDefinitions());
    }
}