Right now do !

[JAVA] JUnit test method의 Locale 지정 그리고 동시성 이슈

by 지금당장해

프롤로그

 이번 글은 다국어 리소스가 포함되어 있는 프로젝트에서 다국어 문자열을 대상으로 하는 테스트가 필요한 경우에 대한 이야기다. 이런 상황에서 단정문을 작성하는 개발자는 혼란을 경험하게 된다.

@Test
void testGetMessage() {
    assertEquals("이 클래스 [{0}]는 생성할 수 없습니다." , Global.getMessage(Global.CANNOT_BE_CREATED, "CTest"));
}

위와 같은 테스트가 내 한글 윈도우10에서는 잘 돌아 갔었는데 CI 서버로 올라가 빌드를 하면 위 테스트의 단정 메서드는 fail을 뱉어낼 것이다. 우리 빌드 서버 역할을 하는 github는 영문 OS를 제공하기 때문이다. 

필자가 격은 이 상황을 해결해 나간 과정을 정리하려 한다. 참고로 필자가 진행하고 있는 프로젝트에서는 JUnit5를 사용하고 있다.

기본 로케일 변경

 접근은 아주 쉽게 했다. 내 개발 머신 테스트 머신 할 것 없이 테스트는 모두 영문으로만 한다는 원칙을 세웠다. 그리고 단정문에 기대 값을 모두 영문으로 고쳤다. 그리고 다국어 리소스를 읽어오는 내 라이브러리에서 무조건 영문으로 반환하게 했어야 했는데 지금은 테스트 중이니 무조건 영문으로 뱉어라고 하기엔 .... 그런 코드가 프로덕트에 들어가는 것이 왠지 내키지도 않고 해서 다른 방법을 취했다. 모든 테스트 전에 다음 코드가 실행되도록 했다.

@BeforeEach
void BeforeEach() {
    // 예외 메시지를 영문으로 assert 하기위한 조치
    Locale.setDefault(new Locale("en", "US"));
}

개별 테스트 결과 성공!!!

 

헉!!! 그런데 우리가 사용하고 있는 빌드 도구인 gradle을 이용하여 빌드를 하면 빌드 마지막 단계인 테스트 과정에서 한글 리소스를 읽어 들여와 생기는 테스트 실패건이 발견되었다. 

org.opentest4j.AssertionFailedError: expected: <This class [CTest] cannot be created> but was: <이 클래스 [CTest]는 생성할 수 없습니다.>

테스트 에이전트의 동시성 문제

  분명히 기본 Locale을 변경했고 개별적인 단위 테스트는 모두 통과했는데 왜 이러는거지 ??? 그렇다 테스트의 동시성 문제가 발생한 것이다. 빌드 도구를 통해서 테스트를 실행하면 여러 테스트가 수행하는데 순차적으로 하나 하나가 아닌 동시 실행을 하는 듯 했다. 제일 좋은 방법은 각 개별 테스트가 독립적으로 동작하면 여서 스레드가 돌던 말던 상관이 없는데 안타깝게도 Locale.setDefault() 메서드가 영향을 미치는 범위는 해당 JVM 이다. 즉 스레드간 공유가 되는 요소라는 것이다.

Sets the default locale for this instance of the Java Virtual Machine. This does not affect the host locale. If there is a security manager, its checkPermission method is called with a PropertyPermission("user.language", "write") permission before the default locale is changed. The Java Virtual Machine sets the default locale during startup based on the host environment. It is used by many locale-sensitive methods if no locale is explicitly specified.

결국 테스트를 작성하는 내가 동시성을 회피 했어야 했다. 일단 필자는 가장 무식한 방법을 취했다. 해당 프로젝트의 모든 테스트를 같은 스레드에서 동작 하도록 제약을 했다.

 

방법은 다음과 같다. 

  1. gradle이나 maven 프로젝트에 test/resources 하위에 junit-platform.properties 파일을 생성한다.
  2. 이 파일을 열어 다음과 같이 작성한다.
# suppress inspection "UnusedProperty" for whole file
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.classes.default = concurrent
junit.jupiter.execution.parallel.mode.default = same_thread

이 문제까지 해결 했다.

 

애필로그

필자가 참고한 JUunit5의 github 사이트에서는 이를 개별 테스트 메서드에 적용하는 방법도 제시하고 있다. 개발자의 상황에 맞게 잘 적용하기 바란다.

참고자료

https://github.com/junit-team/junit5/issues/2142

블로그의 정보

지금 당장 해!!!

지금당장해

활동하기