1. 소개

테스트를 순차적으로 실행하는 것이 대부분의 경우 잘 작동하지만 속도를 높이기 위해 테스트를 병렬화해야 할 수도 있습니다.

이 예제에서는 JUnit과 Maven의 Surefire Plugin을 사용하여 테스트를 병렬화하는 방법을 다룰 것입니다. 먼저 단일 JVM 프로세스에서 모든 테스트를 실행한 다음 다중 모듈 프로젝트로 시도합니다.

2. 메이븐 의존성

필요한 의존성을 가져오는 것으로 시작하겠습니다. Surefire 2.16 이상 과 함께 JUnit 4.7 이상 을 사용해야 합니다 .

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.21.0</version>
</plugin>

간단히 말해서 Surefire는 테스트를 병렬로 실행하는 두 가지 방법을 제공합니다.

  • 단일 JVM 프로세스 내부의 멀티스레딩
  • 여러 JVM 프로세스 포크

3. 병렬 테스트 실행

테스트를 병렬로 실행하려면 org.junit.runners.ParentRunner 를 확장하는 테스트 러너를 사용해야 합니다 .

그러나 기본 실행기가 이 클래스를 확장하므로 명시적 테스트 실행기를 선언하지 않은 테스트도 작동합니다.

다음으로, 병렬 테스트 실행을 시연하기 위해 각각 몇 가지 메서드가 있는 두 개의 테스트 클래스가 있는 테스트 스위트를 사용합니다. 실제로 JUnit 테스트 스위트의 모든 표준 구현이 가능합니다.

3.1. 병렬 매개변수 사용

먼저  병렬  매개변수를 사용하여 Surefire에서 병렬 동작을 활성화합니다. 병렬 처리를 적용하려는 세분성 수준을 나타냅니다.

가능한 값은 다음과 같습니다.

  • methods –  별도의 스레드에서 테스트 메서드를 실행합니다.
  • 클래스 – 별도의 스레드에서 테스트 클래스 실행
  • classesAndMethods – 별도의 스레드에서 클래스와 메서드를 실행합니다.
  • suites – 제품군을 병렬로 실행
  • suitesAndClasses -  별도의 스레드에서 스위트와 클래스를 실행합니다.
  • suitesAndMethods –  클래스와 메서드에 대해 별도의 스레드를 생성합니다.
  • 모두 - 별도의 스레드에서 제품군, 클래스 및 메서드를 실행합니다.

이 예에서는 다음을 모두 사용합니다 .

<configuration>
    <parallel>all</parallel>
</configuration>

둘째, Surefire에서 생성하려는 총 스레드 수를 정의합니다. 두 가지 방법으로 이를 수행할 수 있습니다.

 Surefire가 생성할 최대 스레드 수를 정의하는 threadCount 를 사용하여 다음을 생성합니다.

<threadCount>10</threadCount>

또는 CPU 코어당 하나의 스레드가 생성되는 useUnlimitedThreads 매개변수를 사용합니다.

<useUnlimitedThreads>true</useUnlimitedThreads>

기본적으로 threadCount 는 CPU 코어당입니다. perCoreThreadCount 매개변수를 사용하여  이 동작을 활성화하거나 비활성화할 수 있습니다.

<perCoreThreadCount>true</perCoreThreadCount>

3.2. 스레드 수 제한 사용

이제 메서드, 클래스 및 제품군 수준에서 생성할 스레드 수를 정의하려고 한다고 가정해 보겠습니다. threadCountMethods , threadCountClassesthreadCountSuites 매개변수 를 사용하여 이를 수행할 수 있습니다  .

이 매개변수를  이전 구성의 threadCount 와 결합해 보겠습니다. 

<threadCountSuites>2</threadCountSuites>
<threadCountClasses>2</threadCountClasses>
<threadCountMethods>6</threadCountMethods>

모두 병렬로  사용 했기 때문에 메서드, 제품군 및 클래스에 대한 스레드 수를 정의했습니다. 그러나 리프 매개변수를 정의하는 것이 필수는 아닙니다. Surefire는 리프 매개변수가 생략된 경우 사용할 스레드 수를 추론합니다.

예를 들어,  threadCountMethods 가 생략된  경우 threadCount  >  threadCountClasses threadCountSuites만 확인하면 됩니다.

때로는 무제한의 스레드를 사용하는 동안에도 클래스, 제품군 또는 메서드에 대해 생성되는 스레드 수를 제한해야 할 수 있습니다.

다음과 같은 경우에도 스레드 수 제한을 적용할 수 있습니다.

<useUnlimitedThreads>true</useUnlimitedThreads>
<threadCountClasses>2</threadCountClasses>

3.3. 시간 초과 설정

때로는 테스트 실행에 시간 제한이 있는지 확인해야 할 수도 있습니다.

이를 위해 우리는  parallelTestTimeoutForcedInSeconds  매개변수를 사용할 수 있습니다. 이렇게 하면 현재 실행 중인 스레드가 중단되고 제한 시간이 경과한 후에는 Queue에 있는 스레드가 실행되지 않습니다.

<parallelTestTimeoutForcedInSeconds>5</parallelTestTimeoutForcedInSeconds>

또 다른 옵션은  parallelTestTimeoutInSeconds 를 사용하는 것 입니다.

이 경우 Queue에 있는 스레드만 실행이 중지됩니다.

<parallelTestTimeoutInSeconds>3.5</parallelTestTimeoutInSeconds>

그럼에도 불구하고 두 옵션 모두 시간 초과가 경과하면 테스트가 오류 메시지와 함께 종료됩니다.

3.4. 주의 사항

 Surefire 는 부모 스레드에서 @Parameters , @BeforeClass@AfterClass 어노테이션이 달린 정적 메서드를 호출 합니다. 따라서 테스트를 병렬로 실행하기 전에 잠재적인 메모리 불일치 또는 경쟁 조건을 확인하십시오.

또한 공유 상태를 변경하는 테스트는 확실히 병렬로 실행하기에 좋은 후보가 아닙니다.

4. 다중 모듈 Maven 프로젝트에서 테스트 실행

지금까지 Maven 모듈 내에서 테스트를 병렬로 실행하는 데 중점을 두었습니다.

그러나 Maven 프로젝트에 여러 모듈이 있다고 가정해 보겠습니다. 이러한 모듈은 순차적으로 빌드되므로 각 모듈에 대한 테스트도 순차적으로 실행됩니다.

모듈을 병렬로 빌드 하는 Maven의 -T 매개변수를 사용하여 이 기본 동작을 변경할 수 있습니다 . 이는 두 가지 방법으로 수행할 수 있습니다.

프로젝트를 빌드하는 동안 사용할 정확한 스레드 수를 지정할 수 있습니다.

mvn -T 4 surefire:test

또는 휴대용 버전을 사용하고 CPU 코어당 생성할 스레드 수를 지정합니다.

mvn -T 1C surefire:test

어느 쪽이든 테스트 속도를 높이고 실행 시간을 빌드할 수 있습니다.

5. JVM 포크

병렬  옵션 을 통한 병렬 테스트 실행으로 스레드를 사용하는 JVM 프로세스 내에서 동시성이 발생합니다 .

스레드가 동일한 메모리 공간을 공유하므로 메모리와 속도 측면에서 효율적일 수 있습니다. 그러나 예상치 못한 경쟁 조건이나 기타 미묘한 동시성 관련 테스트 오류가 발생할 수 있습니다. 결과적으로 동일한 메모리 공간을 공유하는 것은 축복이자 저주가 될 수 있습니다.

스레드 수준 동시성 문제를 방지하기 위해 Surefire는 분기 및 프로세스 수준 동시성 이라는 또 다른 병렬 테스트 실행 모드를 제공합니다 . 분기 프로세스의 아이디어는 실제로 매우 간단합니다. Surefire는 여러 스레드를 생성하고 스레드 간에 테스트 메서드를 배포하는 대신 새 프로세스를 생성하고 동일한 배포를 수행합니다.

서로 다른 프로세스 간에 공유 메모리가 없기 때문에 이러한 미묘한 동시성 버그로 인해 어려움을 겪지 않을 것입니다. 물론 이것은 더 많은 메모리 사용량과 약간의 속도 저하를 희생합니다.

어쨌든 분기를 활성화하려면  forkCount  속성을 사용하고 양수 값으로 설정하기만 하면 됩니다.

<forkCount>3</forkCount>

여기서 surefire는 JVM에서 최대 3개의 포크를 생성하고 테스트를 실행합니다. forkCount  의 기본값  은 1이며, 이는 maven-surefire-plugin 이 하나의 Maven 모듈에서 모든 테스트를 실행하기 위해 하나의 새로운 JVM 프로세스를 생성함을 의미합니다.

forkCount  속성 은  -T 와 동일한 구문을 지원합니다 . 즉, 값에 를 추가하면 해당 값에 시스템에서 사용 가능한 CPU 코어 수가 곱해집니다. 예를 들어:

<forkCount>2.5C</forkCount>

그런 다음 2코어 시스템에서 Surefire는 병렬 테스트 실행을 위해 최대 5개의 포크를 생성할 수 있습니다.

기본적으로 Surefire는 생성된 포크를 다른 테스트에 재사용합니다 . 하지만,  reuseForks  속성을  false 로 설정하면 하나의 테스트 클래스를 실행한 후 각 분기를 파괴합니다.

또한 fork를 비활성화하기 위해  forkCount  를 0으로 설정할 수 있습니다.

6. 결론

요약하자면, 다중 스레드 동작을 활성화하고 병렬 매개변수를 사용하여 병렬도를 정의하는 것으로 시작했습니다. 그런 다음 Surefire가 생성해야 하는 스레드 수에 제한을 적용했습니다. 나중에 테스트 실행 시간을 제어하기 위해 제한 시간 매개변수를 설정합니다.

마지막으로 다중 모듈 Maven 프로젝트에서 빌드 실행 시간과 테스트 실행 시간을 줄이는 방법을 살펴보았습니다.

항상 그렇듯이 여기에 제시된 코드는 GitHub 에서 사용할 수 있습니다 .

Junit footer banner