web-dev-qa-db-ja.com

春のテストコンテキストのベストプラクティス

統合テストで巨大なSpringBootアプリケーションをカバーしようとしています。アプリ内にはたくさんのSpringBeanがあります。 Springコンテキストのロードにはしばらく時間がかかります。

だから私は疑問に思っています-

  • Springは、異なるクラスにある複数の統合テスト間で同じコンテキストを共有するのに十分賢いですか?各テストクラスの重いコンテキストの初期化を回避することを意味します。
  • テスト1、2、4がTestContextOneを使用し、テスト3、5がTestContextTwoを使用するとどうなりますか? Springはそれらを1、2、4、3、5の順序で起動しますか?または、Springは2つのコンテキストをメモリに保持しますか?

PSつまり、は、単一の「フル」を使用する一般的な方法です。テストごとに個別のテストを作成する代わりに、すべての統合テストのSpring Context?

10
VB_

アプリケーションをテストするためにSpringFrameworkによって提供される主な機能の1つは、負荷のオーバーヘッドについて言及したことを正確に回避するためのコンテキストキャッシュメカニズムです。春のドキュメントには次のように書かれています。

TestContextフレームワークがテスト用にApplicationContext(またはWebApplicationContext)をロードすると、そのコンテキストはキャッシュされ、同じテストスイート内で同じ一意のコンテキスト構成を宣言する後続のすべてのテストで再利用されます。

この確認を念頭に置いて、テストの構築に最適な戦略を決定するために、キャッシュメカニズムがどのように機能するかを理解する必要があります。ここでの質問は次のとおりです。When spring caches the context, it stores this context in memory using what key?。ドキュメントによると、キーはコンテナのいくつかのパラメータに基づいています。

ApplicationContextは、ロードに使用される構成パラメーターの組み合わせによって一意に識別できます。したがって、構成パラメーターの一意の組み合わせを使用して、コンテキストがキャッシュされるkeyが生成されます。 TestContextフレームワークは、次の構成パラメーターを使用して、コンテキストキャッシュキーを構築します。

locations(@ ContextConfigurationから)
classes(@ ContextConfigurationから)
contextInitializerClasses(@ ContextConfigurationから)
contextCustomizers(ContextCustomizerFactoryから)
contextLoader(@ ContextConfigurationから)
parent(@ ContextHierarchyから)
activeProfiles(@ ActiveProfilesから)
propertySourceLocations(@ TestPropertySourceから)
propertySourceProperties(@ TestPropertySourceから)
resourceBasePath(@ WebAppConfigurationから)

この情報に基づいて、ベストプラクティスは、同じコンテキストパラメータのセット(つまり、同じキャッシュキー)を使用してキャッシュメカニズムの恩恵を受け、別のコンテキストが読み込まれないようにテストを整理することです。 Springのドキュメントにも例があります。

...、TestClassAが@ContextConfigurationのlocations(またはvalue)属性に{"app-config.xml"、 "test-config.xml"}を指定した場合、TestContextフレームワークは対応するApplicationContextをロードし、それらの場所のみに基づくキーの下で、静的コンテキストキャッシュに保存します。したがって、TestClassBがその場所に対して{"app-config.xml"、 "test-config.xml"}も定義しているが(継承を通じて明示的または暗黙的に)、@ WebAppConfigurationを定義していない場合、異なるContextLoader、異なるアクティブなプロファイル、異なるコンテキスト初期化子、異なるテストプロパティソース、または異なる親コンテキスト同じApplicationContextが両方のテストクラスで共有されます。これは、アプリケーションコンテキストをロードするためのセットアップコストが(テストスイートごとに)1回だけ発生し、その後のテスト実行がはるかに高速であることを意味します。

10
user6119452

統合テストで使用できるもう1つのトリックは、コンテキスト内のすべてのBeanを強制的に「遅延」させることです。これは、アプリケーションコンテキスト全体がロードされて初期化されるのを待つ必要がないため、統合テストを1つだけ実行する場合に非常に便利です。これにより、単一のテストの実行にかかる時間を大幅に改善できます。

Beanが暗黙的に作成されている状況に遭遇する可能性があります(例:SpringIntegrationFlow)。フローが直接注入されることはありませんが、クラスにはフローが作成するBeanへの参照がある場合があります。この場合、フローを@Autowireする必要があります(暗黙のBeanが確実に作成されるようにするため)、またはBeanPostProcessorを使用してクリエイティブにすることができます。

次のポストプロセッサを作成しました。テスト用のSpringコンテキストに追加するだけです。

public class LazyInitBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    private Class<?>[] exclusionList;

    public LazyInitBeanFactoryPostProcessor() {
    }

    public LazyInitBeanFactoryPostProcessor(Class<?>[] exclusionList) {
        this.exclusionList = exclusionList;
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

        //Iterate over all bean, mark them as lazy if they are not in the exclusion list.
        for (String beanName : beanFactory.getBeanDefinitionNames()) {
            if (isLazy(beanName, beanFactory)) {
                BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
                definition.setLazyInit(true);
            }
        }
    }

    private boolean isLazy(String beanName, ConfigurableListableBeanFactory beanFactory) {
        if (exclusionList == null || exclusionList.length == 0) {
            return true;
        }
        for (Class<?> clazz : exclusionList) {
            if (beanFactory.isTypeMatch(beanName,clazz)) {
                return false;
            } 
        } 
        return true;        
    }
}

そしてそれを使用するには:

@SpringBootTest(webEnvironment = WebEnvironment.NONE)
public class MyTest {
.
.
.
@TestConfiguration
protected static class TestConfiguration {

    @Bean
    public BeanFactoryPostProcessor lazyBeanPostProcessor() {
        return new LazyInitBeanFactoryPostProcessor();
    }
}

または、除外して拡張します(この例では、Spring Integrationフローに割り当て可能なBeanは、レイジーとしてマークされません。

@TestConfiguration
protected static class TestConfiguration {
    @Bean
    public BeanFactoryPostProcessor lazyBeanPostProcessor() {
        return new ExtendedTestLazyBeanFactoryPostProcessor();
    }


    static private class ExtendedTestLazyBeanFactoryPostProcessor extends LazyInitBeanFactoryPostProcessor {    
        public ServiceTestLazyBeanFactoryPostProcessor() {
            super(new Class<?>[] {IntegrationFlow.class});
        }   
    }
2