[spring] junit 테스트 클래스에서 Spring 애플리케이션 컨텍스트 재사용

많은 JUnit 테스트 케이스 (통합 테스트)가 있으며 논리적으로 서로 다른 테스트 클래스로 그룹화됩니다.

http://static.springsource.org/spring/docs/current/spring-framework-reference에 언급 된대로 테스트 클래스 당 한 번 Spring 애플리케이션 컨텍스트를로드하고 JUnit 테스트 클래스의 모든 테스트 케이스에 재사용 할 수 있습니다 . /html/testing.html

그러나 우리는 JUnit 테스트 클래스 묶음에 대해 Spring 애플리케이션 컨텍스트를 한 번만로드하는 방법이 있는지 궁금했습니다.

FWIW에서는 Spring 3.0.5, JUnit 4.5를 사용하고 Maven을 사용하여 프로젝트를 빌드합니다.



답변

예, 이것은 완벽하게 가능합니다. locations테스트 클래스에서 동일한 속성 을 사용하기 만하면됩니다.

@ContextConfiguration(locations = "classpath:test-context.xml")

Spring은 locations속성 별로 애플리케이션 컨텍스트를 캐시 하므로 locations동일한 컨텍스트가 두 번째로 나타나면 Spring은 새 컨텍스트를 생성하는 대신 동일한 컨텍스트를 사용합니다.

이 기능에 대한 기사를 작성했습니다 : Speeding up Spring integration tests . 또한 Spring 문서에서 자세히 설명합니다 : 9.3.2.1 컨텍스트 관리 및 캐싱 .

이것은 흥미로운 의미를 가지고 있습니다. Spring은 JUnit이 언제 완료되었는지 알지 못하기 때문에 모든 컨텍스트를 영원히 캐시하고 JVM 종료 후크를 사용하여 닫습니다. 이 동작 (특히 다른을 가진 테스트 클래스가 많은 경우 locations)은 과도한 메모리 사용, 메모리 누수 등을 초래할 수 있습니다. 컨텍스트 캐싱의 또 다른 이점입니다.


답변

Tomasz Nurkiewicz의 답변에 추가하기 위해 Spring 3.2.2부터 @ContextHierarchy주석을 사용하여 별도의 연관된 다중 컨텍스트 구조를 가질 수 있습니다. 이는 여러 테스트 클래스가 메모리 내 데이터베이스 설정 (예 : 데이터 소스, EntityManagerFactory, tx 관리자 등)을 공유하려는 경우에 유용합니다.

예를 들면 :

@ContextHierarchy({
  @ContextConfiguration("/test-db-setup-context.xml"),
  @ContextConfiguration("FirstTest-context.xml")
})
@RunWith(SpringJUnit4ClassRunner.class)
public class FirstTest {
 ...
}

@ContextHierarchy({
  @ContextConfiguration("/test-db-setup-context.xml"),
  @ContextConfiguration("SecondTest-context.xml")
})
@RunWith(SpringJUnit4ClassRunner.class)
public class SecondTest {
 ...
}

이 설정을 통해 “test-db-setup-context.xml”을 사용하는 컨텍스트는 한 번만 생성되지만 그 안의 빈은 개별 단위 테스트의 컨텍스트에 주입 될 수 있습니다.

매뉴얼에 대한 추가 정보 : http://docs.spring.io/spring/docs/current/spring-framework-reference/html/testing.html#testcontext-ctx-management ( ” context hierarchy ” 검색 )


답변

기본적으로 Spring은 다른 테스트 클래스에 걸쳐 동일한 애플리케이션 컨텍스트 구성이있는 경우이를 구성 할 수있을만큼 똑똑합니다. 예를 들어 다음과 같이 두 개의 클래스 A와 B가 있다고 가정합니다.

@ActiveProfiles("h2")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class A {

    @MockBean
    private C c;
    //Autowired fields, test cases etc...
}

@ActiveProfiles("h2")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class B {

    @MockBean
    private D d;
    //Autowired fields, test cases etc...
}

이 예제에서 클래스 A는 빈 C를 모의하는 반면 클래스 B는 빈 D를 모의합니다. 따라서 스프링은 이것을 두 개의 다른 구성으로 간주하므로 클래스 A에 대해 한 번, 클래스 B에 대해 한 번 애플리케이션 컨텍스트를로드합니다.

대신 스프링이이 두 클래스간에 애플리케이션 컨텍스트를 공유하도록하려면 다음과 같이 보여야합니다.

@ActiveProfiles("h2")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class A {

    @MockBean
    private C c;

    @MockBean
    private D d;
    //Autowired fields, test cases etc...
}

@ActiveProfiles("h2")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class B {

    @MockBean
    private C c;

    @MockBean
    private D d;
    //Autowired fields, test cases etc...
}

이와 같이 클래스를 연결하면 Spring은 테스트 스위트에서 먼저 실행되는 클래스에 따라 클래스 A 또는 B에 대해 애플리케이션 컨텍스트를 한 번만로드합니다. 이것은 여러 테스트 클래스에 걸쳐 복제 될 수 있지만 기준은 테스트 클래스를 다르게 사용자 정의해서는 안된다는 것입니다. 테스트 클래스가 다른 것과 다른 결과를 초래하는 모든 사용자 정의 (스프링의 관점에서)는 결국 스프링까지 다른 애플리케이션 컨텍스트를 생성하게됩니다.


답변

아래와 같이 구성 클래스를 만듭니다.

@ActiveProfiles("local")
@RunWith(SpringJUnit4ClassRunner.class )
@SpringBootTest(classes ={add your spring beans configuration classess})
@TestPropertySource(properties = {"spring.config.location=classpath:application"})
@ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)
public class RunConfigration {

    private ClassLoader classloader = Thread.currentThread().getContextClassLoader();

    private static final Logger LOG = LoggerFactory.getLogger(S2BXISINServiceTest.class);


    //auto wire all the beans you wanted to use in your test classes
    @Autowired
    public XYZ xyz;
    @Autowired
    public ABC abc;


    }



Create your test suite like below



@RunWith(Suite.class)
@Suite.SuiteClasses({Test1.class,test2.class})
public class TestSuite extends RunConfigration {

    private ClassLoader classloader = Thread.currentThread().getContextClassLoader();

    private static final Logger LOG = LoggerFactory.getLogger(TestSuite.class);


}

아래와 같이 테스트 클래스를 만듭니다.

public class Test1 extends RunConfigration {


  @Test
    public void test1()
    {
    you can use autowired beans of RunConfigration classes here
    }

}


public class Test2a extends RunConfigration {

     @Test
    public void test2()
    {
    you can use autowired beans of RunConfigration classes here
    }


}


답변

한 가지 주목할만한 점은 @SpringBootTests를 사용하지만 다시 사용 use @MockBean in different test classes하면 Spring은 모든 테스트에 대해 애플리케이션 컨텍스트를 재사용 할 방법이 없다는 것입니다.

해결책은 to move all @MockBean into an common abstract class문제를 해결하는 것입니다.

@SpringBootTests(webEnvironment = WebEnvironment.RANDOM_PORT, classes = Application.class)
public abstract class AbstractIT {

   @MockBean
   private ProductService productService;

   @MockBean
   private InvoiceService invoiceService;

}

그런 다음 테스트 클래스는 다음과 같이 볼 수 있습니다.

public class ProductControllerIT extends AbstractIT {
   // please don't use @MockBean here
   @Test
   public void searchProduct_ShouldSuccess() {
   }

}

public class InvoiceControllerIT extends AbstractIT {
   // please don't use @MockBean here
   @Test
   public void searchInvoice_ShouldSuccess() {
   }

}


답변