테스트에서 main - test 파티션 간 소스 간섭 문제 (Classloader와 Url 문제)
통합테스트(@SpringBootTest)에서의 메인(main)과 테스트(test)의 동시 호출 문제
- 현 문제는 reflection 사용 중에 발생한 문제이다.
- 특정 어너테이션을 읽고, 해당 어너테이션의 값이 유일함을 보장해야 한다고 가정한다. 대표적으로 스프링의
@GetMapping
가 그러하다. - 이때 메인과 테스트에서 둘 다 동일한 어너테이션과 value를 구현하고 싶을 수 있다. 예를 들면 아래와 같다.
// main/.../MainController.java
@Controller
public class MainController {
@GetMapping("/main")
public void main(){
// 중략
}
}
// test/.../TestController.java
@Controller
public class TestController {
@GetMapping("/main")
public void main(){
}
}
- 예외가 발생한다!
to {GET [/main]}: There is already 'testController' bean method
; - 왜 이런 문제가 발생할까? 클래스로더가 클래스를 읽을 때 test와 main 폴더 둘 다 읽기 때문이다.
- 실제로 클래스로더가 참조하는 url을 출력하면 아래와 같다.
@SpringBootTest
public class ApplicationTests {
@Test
void contextLoads() {
final Collection<URL> urls = ClasspathHelper.forPackage(SomeClass.class.getPackageName());
for (URL url : urls) {
// 결과가 두 개 나오며 그 url은 아래와 같다. (그래들)
// url = file:/C:/projects/some-project/out/test/classes/
// url = file:/C:/projects/some-project/out/production/classes/
// 메이븐은 /test-classes 라는 경로를 가진다.
System.out.println("url = " + url);
}
}
}
- 이를 해소하기 위하여 main으로 어플리케이션이 동작할 때 test로부터 격리시킨다.
- 다음과 같이 진행한다. 다만 test와 관련한 코드가 main에 삽입되므로 좋은 코드로 보이지 않는다.
@Configuration
public class SomeAnnotationResolver{
@Bean
public SomeResolver someResolver(){
Set<Url> urls = onlyMainUrl();
// 소스코드
}
public Set<Url> onlyMainUrl(){
return ClasspathHelper.forPackage(SomeClass.class.getPackageName()).stream()
.filter(url -> !url.toString().contains("/test-classes")) // 메이븐
.filter(url -> !url.toString().contains("/test/classes")) // 그래들
.collect(Collectors.toSet());
}
}
TODO
- 아래와 같이 해결한 것으로 기억한다. 하지만 명확하지 않으므로 차기 테스트 진행 후 정리한다.
## jar파일과 url의 비정상 호출 문제
- 위와 같은 방식으로 수행할 경우 정상동작하나 jar 파일로 빌딩한 경우 제대로 읽지 못하는 문제가 발생한다.
- 이 경우 `somebean.getClass().getPackage().getName()` 의 형태로 작성해야 한다.