説明
Redis DB への接続を伴う多数の統合テストを伴う成長中のアプリケーションがあります。数が増えているため、少なくともクラスレベルでそれらを並列化したいと考えています。これまで、すべてのテストを順番に実行com.github.kstyrc embedded-redis 0.6
し、静的@BefroreClass
/@AfterClass
メソッド (jUnit 4) に埋め込まれた redis DB ( ) を開始 (停止) しました。DB のポートは常に同じです -- 9736
。これは、jedis 接続プールのapplication.properties
viaにも設定されています。spring.redis.port=9736
並列化が機能するには、ポートを動的に取得し、接続プーリングのために接続ファクトリにアナウンスする必要があります。この問題は、構成に実装することでしばらくして解決しBeanPostProcessor
ました。私が抱えている残りの問題は、Bean のライフサイクルと Web アプリケーションのコンテキストを正しくインターセプトすることです。
コード スニペットの並列テスト
アプリケーションのプロパティ
...
spring.redis.port=${random.int[4000,5000]}
...
実装BeanPostProcessor
構成
@Configuration
public class TestConfig implements BeanPostProcessor {
private RedisServer redisServer;
private int redisPort;
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (JedisConnectionFactory.class.equals(bean.getClass())) {
redisPort = ((JedisConnectionFactory) bean).getPort();
redisServer().start();
}
return bean;
}
@Bean(destroyMethod = "stop")
public RedisServer redisServer() {
redisServer = RedisServer.builder().port(redisPort).build();
return redisServer;
}
}
動的ポートによる並列テストの起動とシャットダウン
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class OfferControllerTest {
private MockMvc mockMvc;
@Inject
protected WebApplicationContext wac;
...
@Before
public void setup() throws Exception {
this.mockMvc = webAppContextSetup(this.wac).apply(springSecurity()).build();
}
@After
public void tearDown() throws Exception {
offerRepository.deleteAll();
}
...
テストの並列化は次の方法で達成されますmaven-surefire-plugin 2.18.1
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
<configuration>
<parallel>classes</parallel>
<threadCount>4</threadCount>
</configuration>
</plugin>
補足
何が起こるかというと、Spring Bean の初期化フェーズ中に、接続プールが開始される前にJedisConnectionFactory
、TestConfig が Bean のライフサイクルにフックし、ランダムに選択されたポートで Redis サーバーを開始します。spring.redis.port=${random.int[4000,5000]}
redisServer 自体は Bean であるため、 を使用しdestroyMethod
て Bean の破棄時にサーバーを停止し、これをアプリケーション コンテキストのライフサイクルに任せます。シーケンシャルからパラレルへの移行は、静的ポートから動的ポートに関してうまくいきました。
問題
しかし、テストを並行して実行すると、次のようなエラーが発生します
java.lang.IllegalStateException: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@22b19d79 has been closed already
。
@Before
public void setup() throws Exception {
this.mockMvc = webAppContextSetup(this.wac).apply(springSecurity()).build();
}
そして
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'spring.redis-org.springframework.boot.autoconfigure.data.redis.RedisProperties': Initialization of bean failed; nested exception is java.lang.IllegalStateException: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@22b19d79 has been closed already
を通して
@After
public void tearDown() throws Exception {
offerRepository.deleteAll();
}
ヘルプ
問題についてよくわかりません。おそらく、tearDown 呼び出しを省略することができ offerRepository.deleteAll()
ます@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
が、セットアップ時のエラーwebAppContextSetup(this.wac).apply(springSecurity()).build()
は依然として残ります。
並行して実行しているときにアプリケーション コンテキストが台無しになりましたか、それともセットアップのアプリケーション コンテキストが既に閉じられているのはなぜですか?
間違ったアプローチ (間違ったパターン) を選択しましたか? もしそうなら、私たちは何を変えるべきですか?