JUnit

Junit 5 for String Boot

Gradle νŒŒμΌμ— μΆ”κ°€

κΈ°μ‘΄ λ‹€λ₯Έ μ˜μ‘΄μ„± λΌμ΄λΈŒλŸ¬λ¦¬μ™€ λ³„λ„λ‘œ junit-jupiter:5.5.2 μΆ”κ°€









Β 


dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
  implementation 'org.springframework.boot:spring-boot-starter-web'
  compileOnly 'org.projectlombok:lombok'
  developmentOnly 'org.springframework.boot:spring-boot-devtools'
  annotationProcessor 'org.projectlombok:lombok'
  testImplementation 'org.springframework.boot:spring-boot-starter-test'

  testCompile 'org.junit.jupiter:junit-jupiter:5.5.2';
}

Spring MockMvc μ„ΈνŒ…

SpringTestUpport.java

import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;

@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = ShortenApplication.class)
public abstract class SpringTestSupport { }

SpringMockMvcTestSupport.java

@AutoConfigureMockMvc μ–΄λ…Έν…Œμ΄μ…˜μ„ μ΄μš©ν•˜μ—¬ μžλ™μœΌλ‘œ κ΅¬μ„±ν•œλ‹€.





Β 






import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.test.web.servlet.MockMvc;

@AutoConfigureMockMvc
public class SpringMockMvcTestSupport extends SpringTestSupport{

    @Autowired
    public MockMvc mockMvc;
}

κΈ°λ³Έ μ‚¬μš©λ²•

MVC ν…ŒμŠ€νŠΈν•˜κΈ°

μ•„λž˜μ™€ 같이 SpringMockMvcTestSupport 을 상속받아 μ‚¬μš©

mockMvc 객체에 perform λ©”μ„œλ“œλ₯Ό μ΄μš©ν•˜μ—¬ ν™•μΈν•œλ‹€.

class ShortenWebControllerTest extends SpringMockMvcTestSupport {
  /**
  * Get으둜 ν˜ΈμΆœν•˜λ©΄ λ‹€μŒκ³Ό 같이 λ™μž‘
  * <pre> - HTTP μ½”λ“œλŠ” 200</pre>
  * <pre> - λ°˜ν™˜λ  λ·°λŠ” index</pre>
  * @see ShortenWebController#getPage()
  * @throws Exception ResultMatcher
  */
  @Test
  @DisplayName("인덱슀 νŽ˜μ΄μ§€ μš”μ²­")
  void getPage_001() throws Exception {
      mockMvc.perform(
          get("/")
      ).andExpect(
          status().isOk()
      ).andExpect(
          view().name("index")
      );
  }
}

@ParameterizedTest μ–΄λ…Έν…Œμ΄μ…˜μ„ μ΄μš©ν•˜μ—¬ μž…λ ₯값을 λ³€ν™”ν•˜μ—¬ ν…ŒμŠ€νŠΈ κ°€λŠ₯ν•˜λ‹€.

/**
 * μ •κ·œμ‹μ„ μ΄μš©ν•˜μ—¬ μœ νš¨ν•œ URL 검증함
 * @param candidate 검증할 URL ν•­λͺ©
 */
@ParameterizedTest
@ValueSource(strings = {
    "1",
    "http:",
    "http://",
    "httpx://www.google.com",
    "httpz://www.google.com",
    "http://com",
    "www.com",
    "google.com",
    "www.google.com"
})
@DisplayName("URL μœ νš¨μ„± 검증")
@Description("HTTP ν”„λ‘œν† μ½œ 및 3μ°¨ 도메인도 ν•„μˆ˜λ‘œ ν¬ν•¨ν•˜μ—¬μ•Ό 함")
void isValid(String candidate) {
    assertFalse(URLValidator.isValid(candidate));
}

@MockBean μ΄λ‚˜ @SpyBean κ³Ό 같이 더미 객체 ν˜Ήμ€ μ‹€μ œ 생성 객체λ₯Ό μ‚¬μš©ν•˜μ—¬ ν…ŒμŠ€νŠΈ ν•˜λŠ” 방법이 μžˆμœΌλ‚˜ 좔후에 μ •λ¦¬ν•˜κΈ°λ‘œ 함

λ™μ‹œμ„± ν…ŒμŠ€νŠΈ ν•˜κΈ°

Fork Join Pool

μ •ν™•νžˆλŠ” Fork Join Framework 라고 ν•˜λŠ” ν”„λ ˆμž„μ›Œν¬μ΄λ‹€. ForkJoinPool 은 이 ν”„λ ˆμž„μ›Œν¬μ˜ λŒ€ν‘œμ μΈ 클래슀이며 기본적으둜 μŠ€λ ˆλ“œ ν’€ μ„œλΉ„μŠ€μ˜ 일쒅이닀.

이 ForkJoinPool 의 κΈ°λ³Έ κ°œλ…μ€ 큰 업무λ₯Ό μž‘μ€ 업무 λ‹¨μœ„λ‘œ μͺΌκ°œκ³ , 각기 λ‹€λ₯Έ CPU μ—μ„œ λ³‘λ ¬λ‘œ μ‹€ν–‰ν•œ ν›„ κ²°κ³Όλ₯Ό μ·¨ν•©ν•˜λŠ” 방식이닀.
마치 λΆ„ν•  정볡 μ•Œκ³ λ¦¬μ¦˜κ³Ό ν‘μ‚¬ν•˜λ©΄ μ—¬λŸ¬ CPU μ½”μ–΄λ₯Ό ν™œμš©ν•˜μ—¬ 동기화와 GC λ₯Ό ν”Όν• μˆ˜ μžˆλŠ” μ—¬λŸ¬ 기법이 μ‚¬μš©λ˜μ—ˆκΈ° λ•Œλ¬Έμ— Java 뿐만이 μ•„λ‹ˆλΌ Scala μ—μ„œλ„ 널리 μ‚¬μš©λ˜κ³  μžˆλŠ” 병렬 처리 기법이닀.

λ‹€μ‹œ μ •λ¦¬ν•˜λ©΄ μ•„λž˜μ™€ κ°™λ‹€.

  1. 큰 업무λ₯Ό μž‘μ€ λ‹¨μœ„λ‘œ μͺΌκ° λ‹€.
  2. λΆ€λͺ¨ μŠ€λ ˆλ“œλ‘œλΆ€ν„° 처리 λ‘œμ§μ„ λ³΅μ‚¬ν•˜μ—¬ μƒˆλ‘œμš΄ μŠ€λ ˆλ“œλ₯Ό μƒμ„±ν•˜μ—¬ 업무λ₯Ό μˆ˜ν–‰ (Fork) μ‹œν‚¨λ‹€.
  3. 2 λ₯Ό λ°˜λ³΅ν•˜λ‹€ νŠΉμ • μŠ€λ ˆλ“œμ—μ„œ 더이상 Fork κ°€ μΌμ–΄λ‚˜μ§€ μ•Šκ³  업무가 μ™„λ£Œλ˜λ©΄ κ·Έ κ²°κ³Όλ₯Ό λΆ€λͺ¨ μŠ€λ ˆλ“œμ—μ„œ Join ν•˜μ—¬ 값을 μ·¨ν•©ν•œλ‹€.
  4. 3 을 λ°˜λ³΅ν•˜λ‹€κ°€ 졜초의 ForkJoinPool 을 μƒμ„±ν•œ μŠ€λ ˆλ“œλ‘œ 값을 λ¦¬ν„΄ν•˜μ—¬ μž‘μ—…μ„ μ™„λ£Œν•œλ‹€.

Fork Join

기본적으둜 ExecutorService κ΅¬ν˜„μ²΄μ΄λ‹€.

일반 ExecutorService κ΅¬ν˜„ν΄λž˜μŠ€μ™€λŠ” 기본적으둜 λ‹€λ₯Έμ μ€ Work-Stealing μ•Œκ³ λ¦¬μ¦˜μ΄ κ΅¬ν˜„λ˜μ–΄ μžˆλ‹€λŠ” 점이닀.

μ΄λŠ” μ΅œλŒ€ν•œ CPU의 Task λΆ„λ°°μ‹œ Idle λ₯Ό μ΅œμ†Œν™” ν•˜κΈ° μœ„ν•΄ κ³ λ €λ˜μ—ˆμœΌλ©° μ΄λŠ” CPU κ°€ μž‘μ—…μ΄ λλ‚œ Idle μƒνƒœκ°€ 되면 λ‹€λ₯Έ λŒ€κΈ°μ—΄μ˜ μž‘μ—…μ„ 가져와 μ²˜λ¦¬ν•˜λŠ” μ‹μ˜ κ΅¬ν˜„μ„ ν•΄μ€€λ‹€.

이와 κ΄€λ ¨ν•œ ForkJoinPool 의 λŒ€ν‘œ ν΄λž˜μŠ€λ‚˜ 예제λ₯Ό μ°Ύμ•„μ„œ ν™•μΈν•΄λ³΄μž

ν”„λ‘œνΌν‹° 파일 μΆ”κ°€

src/test/resources/junit-platform.properties 파일 κ²½λ‘œμ— ν”„λ‘œνΌν‹° νŒŒλΌλ©”ν„° μΆ”κ°€ (μ—†μœΌλ©΄ μƒˆλ‘œ 생성)

junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
junit.jupiter.execution.parallel.mode.classes.default = concurrent

μ½”λ“œ μž‘μ„±

public class λ™μ‹œμ„±_ν…ŒμŠ€νŠΈ {
  public boolean getTrue() {
    return IntStream.rangeClosed(0, Integer.MAX_VALUE)
      .filter(p -> p % 2 == 0)
      .noneMatch(p -> p % 2 == 1);
  }
}
Β 





























@Execution(CONCURRENT)
public class λ™μ‹œμ„±_ν…ŒμŠ€νŠΈTest {

  static λ™μ‹œμ„±_ν…ŒμŠ€νŠΈ t;

  @BeforeAll
  static void BeforeAll() {
    t = new λ™μ‹œμ„±_ν…ŒμŠ€νŠΈ();
  }

  @AfterEach
  void AfterEach() {
    System.out.println("\n");
    System.out.println("After Each");
    System.out.println("\n");
  }

  @RepeatedTest(value = 10)
  @DisplayName("ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€ 1")
  void TEST_CASE_1() {
    assertTimeout(Duration.ofMillis(5000), () -> assertTrue(t.getTrue()));
  }

  @RepeatedTest(value = 10)
  @DisplayName("ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€ 2")
  void TEST_CASE_2() {
    assertTimeout(Duration.ofMillis(5000), () -> assertTrue(t.getTrue()));
  }
}

@BeforeAll 둜 ν…ŒμŠ€νŠΈ μ‹œμž‘μ „μ— μΈμŠ€ν„΄μŠ€ 객체λ₯Ό μƒμ„±ν•˜κ³  각각의 μŠ€λ ˆλ“œλ‘œ 10λ²ˆμ”© 반볡 μˆ˜ν–‰

총 5000 Millisecond 이내에 μˆ˜ν–‰ λͺ©μ μœΌλ‘œ μž‘μ„±ν•¨

λ™μ‹œμ„± ν…ŒμŠ€νŠΈ

λ§Œμ•½ λ™μ‹œμ„± ν…ŒμŠ€νŠΈ 없이 같은 μŠ€λ ˆλ“œλ‘œ μˆ˜ν–‰ν•˜λ €λ©΄ μ•„λž˜μ™€ 같이 @Execution(SAME_THREAD) μ–΄λ…Έν…Œμ΄μ…˜μœΌλ‘œ λ³€κ²½