Backend/JAVA

JUnit ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ ์šฉ, JaCoCo ์ปค๋ฒ„๋ฆฌ์ง€ ์ธก์ •

dddzr 2025. 8. 27. 21:52

๐Ÿ“Œ ๋ชฉ์ฐจ

  1. JUnit์ด๋ž€?
  2. JUnit ์ ์šฉ ๋ฐฉ๋ฒ•
  3. JUnit ์ ์šฉ ์˜ˆ์‹œ
  4. DB ์—ฐ๋™ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ๋ฐฉ๋ฒ•
  5. ํ…Œ์ŠคํŠธ ์‹คํ–‰
  6. ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€๋ž€?
  7. ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€ ์ธก์ • ๋ฐฉ๋ฒ• - JaCoCo
  8. ํ•œ๊ธ€ ๊ฒฝ๋กœ ๋ฌธ์ œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•
  9. ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ํ™•์ธ

๐ŸŽฏ 0.JUnit ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ ์šฉํ•˜๊ธฐ

๊ธฐ์กด ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์ง€ ์•Š๊ณ  ์ง์ ‘ ํ”„๋กœ๊ทธ๋žจ์„ ๋กœ์ปฌ์—์„œ ์‹คํ–‰ํ•ด์„œ ํ…Œ์ŠคํŠธํ•˜๊ณ  ์žˆ์—ˆ๋‹ค.

 => ์ฝ”๋“œ ๋ณ€๊ฒฝ ์‹œ ๋งˆ๋‹ค ๊ธฐ๋Šฅ์„ ์žฌํ…Œ์ŠคํŠธ ํ•˜๋Š”๋ฐ ์‹œ๊ฐ„์ด ์†Œ์š”๋˜๊ณ , ํ…Œ์Šคํ„ฐ๊ฐ€ ๋†“์น˜๋Š” ๋ถ€๋ถ„์ด ์žˆ์„ ์ˆ˜ ์žˆ์Œ.

์ด๋ฅผ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•ด JUnit ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•์„ ์•Œ์•„๋ณด๊ณ  ‘์ผ์ • ์‹œ์Šคํ…œ’์— ์ ์šฉํ•ด๋ณด๊ธฐ๋กœ ํ–ˆ๋‹ค!!

 

๐Ÿ“Œ 1. JUnit์ด๋ž€?

  • Java์—์„œ ๊ฐ€์žฅ ๋งŽ์ด ์“ฐ์ด๋Š” ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ
  • ์—ญํ• : ํ…Œ์ŠคํŠธ ์ž‘์„ฑ → ์ž๋™ ์‹คํ–‰ → ๊ฒฐ๊ณผ ํ™•์ธ
  • Spring Boot์—์„œ๋Š” spring-boot-starter-test์— JUnit 5, Mockito๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋‹ค!!

๐Ÿ“– pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

        <!-- <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <scope>test</scope>
        </dependency> -->

        <!-- Mockito -->
        <!-- <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-junit-jupiter</artifactId>
            <scope>test</scope>
        </dependency> -->

 

โœ… ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

  • ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค: src/test/java
  • ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ: @Test ๋ถ™์ž„

๐Ÿ“– ์˜ˆ์ œ

@SpringBootTest
class MyServiceTest {

    @Autowired
    MyService myService;

    @Test
    void testAdd() {
        int result = myService.add(2, 3);
        assertEquals(5, result);
    }
}

 

๐Ÿ“Œ 2.  JUnit ์ ์šฉ ๋ฐฉ๋ฒ•

โœ… 2-1. ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค ์„ ํƒ

๐Ÿ”น๊ธฐ๋ณธ ์›์น™: ๋ณดํ†ต ํ•˜๋‚˜์˜ ํด๋ž˜์Šค → ํ•˜๋‚˜์˜ ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค

๐Ÿ“– ์˜ˆ: ScheduleServiceImpl.java → ScheduleServiceImplTest.java

๐Ÿ”น๊ทœ์น™

  • ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค ์ด๋ฆ„์€ ๋ณดํ†ต ์›๋ณธ ํด๋ž˜์Šค ์ด๋ฆ„ + Test
  • ํŒจํ‚ค์ง€ ๊ตฌ์กฐ๋Š” ์›๋ณธ ํด๋ž˜์Šค์™€ ๊ฐ™์€ ํŒจํ‚ค์ง€์— ๋‘๋ฉด private ๋ฉ”์„œ๋“œ ํ…Œ์ŠคํŠธ๋‚˜ package-private ์ ‘๊ทผ์ด ํŽธํ•จ

๐Ÿ“– ์˜ˆ

src/main/java/kr/co/calender/service/ScheduleServiceImpl.java

src/test/java/kr/co/calender/service/ScheduleServiceImplTest.java

๐Ÿ”น์˜ˆ์™ธ

  • ์—ฌ๋Ÿฌ ๊ด€๋ จ ๊ธฐ๋Šฅ์„ ๋ฌถ์–ด์„œ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๊ณ  ์‹ถ์œผ๋ฉด → ํ•œ ํŒŒ์ผ์— ์—ฌ๋Ÿฌ ํด๋ž˜์Šค๋ฅผ ํ…Œ์ŠคํŠธํ•  ์ˆ˜๋„ ์žˆ์Œ
  • ๋ฐ˜๋Œ€๋กœ ๋‹จ์ˆœ helper ์œ ํ‹ธ์ด๋ผ๋ฉด → ์—ฌ๋Ÿฌ ์œ ํ‹ธ์„ ํ•œ ํ…Œ์ŠคํŠธ ํŒŒ์ผ์—์„œ ๋‹ค๋ค„๋„ ๋จ

๐Ÿ”น ๐Ÿš€ ๊ฒฐ๋ก 

๋Œ€๋ถ€๋ถ„์€ 1:1 ๋งค์นญ, ํ•„์š”์‹œ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ์šฉ ํŒŒ์ผ ๋”ฐ๋กœ ์ƒ์„ฑ

 

โœ… 2-2. ํ…Œ์ŠคํŠธํ•  ๋ฉ”์„œ๋“œ ์„ ํƒ

๐Ÿ”น๊ณต๊ฐœ(Public) ๋ฉ”์„œ๋“œ ์œ„์ฃผ๋กœ ํ…Œ์ŠคํŠธ

  • Controller → API ํ˜ธ์ถœ ๊ฒฐ๊ณผ ๊ฒ€์ฆ
  • Service → ํ•ต์‹ฌ ๋กœ์ง(๊ณ„์‚ฐ, ์ƒํƒœ ๋ณ€๊ฒฝ, ์˜ˆ์™ธ ์ฒ˜๋ฆฌ) ๊ฒ€์ฆ

๐Ÿ”นprivate ๋ฉ”์„œ๋“œ

  • ์ผ๋ฐ˜์ ์œผ๋กœ ์ง์ ‘ ํ…Œ์ŠคํŠธํ•˜์ง€ ์•Š๊ณ  public ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ๊ฐ„์ ‘ ํ…Œ์ŠคํŠธ
  • ์˜ˆ์™ธ: ๊ผญ ํ•„์š”ํ•˜๋ฉด Reflection์œผ๋กœ ํ˜ธ์ถœ ๊ฐ€๋Šฅ(Method.invoke())

โœ… 2-3. given-when-then ํŒจํ„ด

  • given-when-then ํŒจํ„ด (BDD – Behavior Driven Development ์Šคํƒ€์ผ)
  • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์—์„œ ์กฐ๊ฑด → ์‹คํ–‰ → ๊ฒฐ๊ณผ๋ฅผ ๊ตฌ๋ถ„ํ•ด ๊ฐ€๋…์„ฑ์„ ๋†’์ด๋Š” ํŒจํ„ด


1๏ธโƒฃ Given : ํ…Œ์ŠคํŠธ ์ค€๋น„ (์ž…๋ ฅ๊ฐ’, Mock ๋™์ž‘ ์„ค์ •)

2๏ธโƒฃ When : ์‹ค์ œ ํ…Œ์ŠคํŠธ ๋Œ€์ƒ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ

3๏ธโƒฃ Then : ์‹คํ–‰ ๊ฒฐ๊ณผ ๊ฒ€์ฆ (assert, verify)

 

๐Ÿ“– ์˜ˆ์‹œ

@Test
@DisplayName("์ฃผ๋ฌธ ์ƒ์„ฑ ์„ฑ๊ณต")
void placeOrder_success() {
    // Given (์ƒํƒœ/Mock ์ค€๋น„)  
    when(orderRepository.save(any())).thenReturn(new Order(1L));

    // When (๋ฉ”์„œ๋“œ ์‹คํ–‰)
    Order saved = orderService.placeOrder(new CreateOrderCmd(...));

    // Then (๊ฒ€์ฆ)
    assertThat(saved.getId()).isEqualTo(1L);
    verify(orderRepository, times(1)).save(any());
}

 

 

โœ… 2-4. ์ž์ฃผ ์“ฐ๋Š” JUnit ์–ด๋…ธํ…Œ์ด์…˜

์–ด๋…ธํ…Œ์ด์…˜ ์šฉ๋„
@Test ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ ์ง€์ •
@BeforeEach ๊ฐ ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์ „๋งˆ๋‹ค ์ดˆ๊ธฐํ™” ์ž‘์—… ์ˆ˜ํ–‰
@AfterEach ๊ฐ ํ…Œ์ŠคํŠธ ์‹คํ–‰ ํ›„๋งˆ๋‹ค ์ •๋ฆฌ ์ž‘์—… ์ˆ˜ํ–‰
@BeforeAll ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค ์‹œ์ž‘ ์ „ ํ•œ ๋ฒˆ ์‹คํ–‰ (static)
@AfterAll ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค ์ข…๋ฃŒ ํ›„ ํ•œ ๋ฒˆ ์‹คํ–‰ (static)
@Disabled ํŠน์ • ํ…Œ์ŠคํŠธ ์ž„์‹œ๋กœ ์ œ์™ธ
@DisplayName("์„ค๋ช…") ํ…Œ์ŠคํŠธ ์ด๋ฆ„์— ์„ค๋ช… ์ถ”๊ฐ€ ๊ฐ€๋Šฅ

 

 

โœ… 2-5. Mock ๊ฐ์ฒด ์‚ฌ์šฉ

๐Ÿ” 2-5-1. Mock์ด๋ž€?

  • ๊ฐ€์งœ ํ˜‘๋ ฅ์ž ๊ฐ์ฒด
  • ํ˜ธ์ถœ ์‹œ ๋ฐ˜ํ™˜๊ฐ’์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๊ณ , ํ˜ธ์ถœ ์—ฌ๋ถ€/ํšŸ์ˆ˜/ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ์Œ

 

๐Ÿ” 2-5-2. Mockito๋ž€?

  • ๊ฐ€์žฅ ๋งŽ์ด ์“ฐ์ด๋Š” ์ž๋ฐ” Mocking ํ”„๋ ˆ์ž„์›Œํฌ
  • ํ…Œ์ŠคํŠธ ๋Œ€์ƒ ํด๋ž˜์Šค์˜ ํ˜‘๋ ฅ ๊ฐ์ฒด๋ฅผ ๊ฐ€์งœ(Mock)๋กœ ์ƒ์„ฑํ•˜๊ณ , ๋™์ž‘์„ ์ •์˜ํ•˜๊ฑฐ๋‚˜ ํ˜ธ์ถœ์„ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์คŒ
  • JUnit๊ณผ ํ•จ๊ป˜ ์“ฐ๋ฉด ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ์ด ํ›จ์”ฌ ๊ฐ„๋‹จํ•ด์ง


๐Ÿ“– ์˜ˆ์ œ

// Mock ์ƒ์„ฑ
UserRepository repo = mock(UserRepository.class);

// ํ–‰๋™ ์ •์˜ (Stub)
when(repo.findById(1L)).thenReturn(new User(1L, "ํ™๊ธธ๋™"));

// ํ˜ธ์ถœ & ๊ฒฐ๊ณผ ๊ฒ€์ฆ
verify(repo).findById(1L);
assertEquals(3, result.getUsersList().size());

 

๐Ÿ“– *BDD ์Šคํƒ€์ผ (BDDMockito) - when๊ณผ ๋™์ผ ๊ธฐ๋Šฅ, ๋‹ค๋ฅธ ์Šคํƒ€์ผ

given(repo.findById(1L)).willReturn(new User(1L, "ํ™๊ธธ๋™"));



๐Ÿ”น2-5-3. @MockBean vs @Mock vs @InjectMocks

  • @Mock: ๊ฐ€์งœ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด ๋ฐ˜ํ™˜ํ•ด์ฃผ๋Š” ์–ด๋…ธํ…Œ์ด์…˜ -> ํ…Œ์ŠคํŠธ ๋Œ€์ƒ ๋‚ด๋ถ€์—์„œ ์‚ฌ์šฉํ•˜๋Š” ํด๋ž˜์Šค
  • @Spy: Stubํ•˜์ง€ ์•Š์€ ๋ฉ”์†Œ๋“œ๋“ค์€ ์›๋ณธ ๋ฉ”์†Œ๋“œ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜
  • @InjectMocks: ํ…Œ์ŠคํŠธ ๋Œ€์ƒ ํด๋ž˜์Šค ์ƒ์„ฑ ๋ฐ Mock(Mock/Spy๋กœ ์ƒ์„ฑ๋œ ๊ฐ€์งœ ๊ฐ์ฒด) ์ฃผ์ž…  -> ํ…Œ์ŠคํŠธ ๋Œ€์ƒ ํด๋ž˜์Šค
  • *@MockBean : Spring ์ปจํ…์ŠคํŠธ์—์„œ ์‹ค์ œ Bean ๋Œ€์ฒด (Spring boot ํ™˜๊ฒฝ, ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ์šฉ)

๐Ÿ’ก ํŒ

  • @InjectMocks → ๊ตฌํ˜„์ฒด ๋Œ€์ƒ์œผ๋กœ๋งŒ ํ…Œ์ŠคํŠธ (์ธํ„ฐํŽ˜์ด์Šค๋Š” ๋กœ์ง ์—†์Œ)
  • @MockBean → ์ธํ„ฐํŽ˜์ด์Šค ํƒ€์ž…์œผ๋กœ ๋“ฑ๋กํ•˜๋Š” ๊ฒŒ ์ผ๋ฐ˜์ 

 

๐Ÿ”น 2-4-4. ์–ธ์ œ ๋ญ˜ ์“ฐ์ง€?

๐Ÿ“– ์„œ๋น„์Šค ๋‹จ์œ„ ํ…Œ์ŠคํŠธ (Repository๋งŒ Mock)

์„œ๋น„์Šค ๋ฉ”์„œ๋“œ ๋‚ด์—์„œ ์‹คํ–‰๋˜๋Š” Repository, Mapper๋ฅผ Mock์œผ๋กœ ๋“ฑ๋ก, .thenReturn()์œผ๋กœ ๊ฐ’์ง€์ •ํ•˜์—ฌ ์‹ค์ œ DBํ˜ธ์ถœ ์—†์ด ํ…Œ์ŠคํŠธํ•œ๋‹ค.

-> @Mock ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด ๊ฐ€์งœ UserRepository๋ฅผ ๋งŒ๋“ค๊ณ , @InjectMocks๋ฅผ ํ†ตํ•ด User Service ์— ์ด๋ฅผ ์ฃผ์ž…

@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
    @Mock private OrderRepository orderRepository; // ์ปจํ…์ŠคํŠธ ๋“ฑ๋ก X
    @InjectMocks private OrderServiceImpl orderService; // ํ…Œ์ŠคํŠธ ๋Œ€์ƒ
    
    @Test
    void placeOrder_success() {
        when(orderRepository.save(any())).thenReturn(new Order(1L));
        Order saved = orderService.placeOrder(new CreateOrderCmd(...));
        assertThat(saved.getId()).isEqualTo(1L);
        verify(orderRepository, times(1)).save(any());
    }
}
  • ๋น ๋ฆ„, ์Šคํ”„๋ง ์•ˆ ๋„์›€ → ์ˆœ์ˆ˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๊ฒ€์ฆ์— ์ตœ์ .

๐Ÿ“– ์ปจํŠธ๋กค๋Ÿฌ ์Šฌ๋ผ์ด์Šค ํ…Œ์ŠคํŠธ (์„œ๋น„์Šค๋งŒ Mock) → @WebMvcTest + @MockBean

@WebMvcTest(OrderController.class)
class OrderControllerTest {
    @Autowired private MockMvc mvc;
    @MockBean private OrderService orderService; // ์ปจํ…์ŠคํŠธ์— "์„œ๋น„์Šค ๋นˆ"์œผ๋กœ ๋“ฑ๋ก & ์‹ค์ œ ๋นˆ ๋Œ€์ฒด

    @Test
    void getOrder_returns200() throws Exception {
        when(orderService.get(1L)).thenReturn(new OrderDto(1L, "OK"));
        mvc.perform(get("/orders/1"))
           .andExpect(status().isOk())
           .andExpect(jsonPath("$.id").value(1L));
        verify(orderService).get(1L);
    }
}
  • MVC ๋นˆ๋งŒ ๋กœ๋”ฉ → ์ปจํŠธ๋กค๋Ÿฌ-์„œ๋น„์Šค ๊ฒฝ๊ณ„ ๊ฒ€์ฆ์— ์ ํ•ฉ

 

โœ… 2-5. Assert ์‚ฌ์šฉ

JUnit์—์„œ ๊ฒ€์ฆ(assertion) ์šฉ๋„๋กœ ์‚ฌ์šฉ

  • assertEquals(expected, actual) : ๊ฐ’ ๋น„๊ต
  • assertNotNull(object) : null ์—ฌ๋ถ€ ํ™•์ธ
  • assertTrue(condition) : ์กฐ๊ฑด true ํ™•์ธ
  • assertFalse(condition) : ์กฐ๊ฑด false ํ™•์ธ
  • assertThrows(Exception.class, () -> method()) : ์˜ˆ์™ธ ๋ฐœ์ƒ ๊ฒ€์ฆ
  • assertAll(executables...) : ๋ชจ๋“  assertion์„ ์‹คํ–‰, ์ผ๋ถ€ ์‹คํŒจํ•ด๋„ ๋‚˜๋จธ์ง€ assertion ์‹คํ–‰
assertAll(
    () -> assertEquals(1, result),
    () -> assertNotNull(obj),
    () -> verify(scheduleMapper).selectSchedule("SCHD001"),
);

 

 

โœ… 2-6. verify 

  • ํ•ด๋‹น Mock ๊ฐ์ฒด์˜ ํŠน์ • ๋ฉ”์„œ๋“œ๊ฐ€ ์‹ค์ œ๋กœ ํ˜ธ์ถœ๋๋Š”์ง€ ํ™•์ธ

๐Ÿ“– ๊ธฐ๋ณธ ์‚ฌ์šฉ

verify(scheduleMapper).updateSchedule(any(ScheduleDTO.class));

 

๐Ÿ“– ํšŸ์ˆ˜ ์ œ์–ด

  • times(n) : n๋ฒˆ ํ˜ธ์ถœ๋๋Š”์ง€ ํ™•์ธ
  • never() : ํ˜ธ์ถœ๋˜์ง€ ์•Š์•˜๋Š”์ง€ ํ™•์ธ
  • atLeast(n) / atMost(n) : ์ตœ์†Œ/์ตœ๋Œ€ ํ˜ธ์ถœ ํšŸ์ˆ˜ ํ™•์ธ
verify(scheduleMapper, times(1)).updateSchedule(any());
verify(scheduleMapper, never()).deleteScheduleSharedUsers("SCHD002");
verify(scheduleMapper, atLeast(2)).insertScheduleSharedUser(any());
verify(scheduleMapper, atMost(3)).selectSchedule("SCHD001");

 

๐Ÿ“– ์ˆœ์„œ ์ œ์–ด (InOrder)

InOrder inOrder = inOrder(scheduleMapper);
inOrder.verify(scheduleMapper).selectSchedule("SCHD001");
inOrder.verify(scheduleMapper).deleteScheduleSharedUsers("SCHD001");
inOrder.verify(scheduleMapper).insertScheduleSharedUser(any());

 

โœ… 2-6. Reflection ์‚ฌ์šฉ (private ๋ฉ”์„œ๋“œ ํ…Œ์ŠคํŠธ)

  • ๋ณดํ†ต์€ public ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ๊ฐ„์ ‘ ๊ฒ€์ฆํ•˜๋Š” ๊ฒŒ ์›์น™
  • ๊ผญ ํ•„์š”ํ•  ๋•Œ๋งŒ ์‚ฌ์šฉ
Method privateMethod = MyService.class.getDeclaredMethod("privateCalculate", int.class);
privateMethod.setAccessible(true); // ์ ‘๊ทผ ์ œํ•œ ํ•ด์ œ
int result = (int) privateMethod.invoke(myService, 10); // ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ
assertEquals(20, result);

 

 

๐Ÿ“Œ 3. JUnit ์ ์šฉ ์˜ˆ์‹œ

๐Ÿ’ก Tip

  • ํ•ต์‹ฌ ๋กœ์ง, ์˜ˆ์™ธ ์ฒ˜๋ฆฌ, ๋ถ„๊ธฐ(if/else) ์œ„์ฃผ๋กœ ํ…Œ์ŠคํŠธ
  • ์ปค๋ฒ„๋ฆฌ์ง€ ์ˆ˜์น˜๋ฅผ ๋†’์ด๋Š” ๊ฒƒ๋ณด๋‹ค ์‹ค์ œ ๋กœ์ง ๊ฒ€์ฆ์ด ๋” ์ค‘์š”
  • DB ์—ฐ๋™์€ Mock ๋˜๋Š” ์ธ๋ฉ”๋ชจ๋ฆฌ DB๋ฅผ ํ™œ์šฉ

โœ… 3-1. Controller/Service ํ…Œ์ŠคํŠธ

  • Spring Boot ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์—์„œ ํ˜ธ์ถœ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ
// 1๏ธโƒฃ Spring Boot ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์—์„œ Controller๋ฅผ ํ…Œ์ŠคํŠธํ•  ๋•Œ ์‚ฌ์šฉ
// webEnvironment = RANDOM_PORT : ์‹ค์ œ ์„œ๋ฒ„ ํฌํŠธ๊ฐ€ ๋žœ๋ค์œผ๋กœ ํ• ๋‹น๋˜์–ด ํ…Œ์ŠคํŠธ ๊ฐ„ ์ถฉ๋Œ ๋ฐฉ์ง€
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

// 2๏ธโƒฃ MockMvc๋ฅผ ์ž๋™ ์„ค์ •ํ•ด์„œ HTTP ์š”์ฒญ/์‘๋‹ต์„ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•จ
@AutoConfigureMockMvc

// 3๏ธโƒฃ ํ…Œ์ŠคํŠธ์šฉ Security ์„ค์ •์„ ๋ถˆ๋Ÿฌ์™€์„œ ์ธ์ฆ/์ธ๊ฐ€ ๋ฌธ์ œ ์—†์ด ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ
@Import(TestSecurityConfig.class)
class ScheduleRestControllerTest {

    // 4๏ธโƒฃ MockMvc ๊ฐ์ฒด ์ฃผ์ž… → Controller ์š”์ฒญ ํ…Œ์ŠคํŠธ์šฉ
    @Autowired
    private MockMvc mockMvc;

    // 5๏ธโƒฃ ์‹ค์ œ Service ํ˜ธ์ถœ์„ Mock ์ฒ˜๋ฆฌ → DB๋‚˜ ๋‹ค๋ฅธ ์˜์กด์„ฑ ์˜ํ–ฅ ์—†์ด ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ
    @MockBean
    private ScheduleService scheduleService;

    // 6๏ธโƒฃ ํ…Œ์ŠคํŠธ ์‹œ์ž‘ ์ „ ์ดˆ๊ธฐํ™” ์ž‘์—…
    @BeforeEach
    void setUp() {
        // ์˜ˆ: Mock ๋ฐ˜ํ™˜๊ฐ’ ์„ค์ •
        List<ScheduleDTO> mockSchedules = new ArrayList<>();
        mockSchedules.add(new ScheduleDTO("2025-08-18", "ํ…Œ์ŠคํŠธ ์ผ์ •"));

        // scheduleService.getSchedules() ํ˜ธ์ถœ ์‹œ mockSchedules ๋ฐ˜ํ™˜
        Mockito.when(scheduleService.getSchedules()).thenReturn(mockSchedules);
    }

    // 7๏ธโƒฃ ์‹ค์ œ API ํ˜ธ์ถœ ํ…Œ์ŠคํŠธ
    @Test
    void testGetSchedules() throws Exception {
        mockMvc.perform(get("/api/schedules")) // GET ์š”์ฒญ ์ˆ˜ํ–‰
               .andExpect(status().isOk())   // HTTP ์ƒํƒœ 200 OK ํ™•์ธ
               .andExpect(jsonPath("$").isArray()) // JSON ๋ฐฐ์—ด ํ˜•์‹์ธ์ง€ ํ™•์ธ
               .andExpect(jsonPath("$[0].title").value("ํ…Œ์ŠคํŠธ ์ผ์ •")); // ์ฒซ ๋ฒˆ์งธ ์š”์†Œ ์ œ๋ชฉ ํ™•์ธ
    }

}

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด Controller → Service → Repository ํ˜ธ์ถœ ํ๋ฆ„์„ ์ „์ฒด์ ์œผ๋กœ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ

 

โœ… 3-2. Spring Security ํ…Œ์ŠคํŠธ

  • ์šด์˜ ํ™˜๊ฒฝ์—์„œ๋Š” Security ๋•Œ๋ฌธ์— ํ…Œ์ŠคํŠธ ์‹คํŒจ ๊ฐ€๋Šฅ → ๋‹จ์œ„ ํ…Œ์ŠคํŠธ์šฉ SecurityConfig ์ถ”๊ฐ€
@TestConfiguration
@EnableWebSecurity
public class TestSecurityConfig {
    @Bean
    public SecurityFilterChain testFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .authorizeHttpRequests(auth -> auth
                .anyRequest().permitAll()
            );
        return http.build();
    }
}
  • ํ…Œ์ŠคํŠธ ์‹œ Security๋ฅผ ์™„์ „ํžˆ ์šฐํšŒ → MockMvc๋กœ Controller ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ

โœ… 3-3. Private ๋ฉ”์„œ๋“œ ํ…Œ์ŠคํŠธ

  • ๋ณดํ†ต Private ๋ฉ”์„œ๋“œ๋Š” ์ง์ ‘ ํ…Œ์ŠคํŠธํ•˜์ง€ ์•Š๊ณ  public ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์‹œ ๊ฐ„์ ‘ ํ…Œ์ŠคํŠธ
  • ๋‹จ, ํ•ต์‹ฌ ๋กœ์ง์ด private์ด๋ฉด Reflection์œผ๋กœ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ

๐Ÿ“– ํ•ต์‹ฌ ๋กœ์ง ํŒ๋‹จ ์˜ˆ์‹œ - ์–ด๋–ค ๋ฉ”์„œ๋“œ๋ฅผ ํ…Œ์ŠคํŠธํ•˜๋Š” ๊ฒŒ ์ข‹์„๊นŒโ“

๋ฉ”์„œ๋“œ ํ…Œ์ŠคํŠธ ํ•„์š”์„ฑ ์ด์œ 
generateRepeatedSchedules โญโญโญโญโญ (ํ•„์ˆ˜) ๋ฐ˜๋ณต ์ฃผ๊ธฐ๋ณ„ ๋‚ ์งœ ๊ณ„์‚ฐ์ด ๋‹ฌ๋ผ์„œ ๋ฒ„๊ทธ ๊ฐ€๋Šฅ์„ฑ ํผ. ์›๋ณธ์ผ์ • ์ฒ˜๋ฆฌ์™€ ์ž˜๋ชป๋œ ์ข…๋ฃŒ์ผ ์ฒ˜๋ฆฌ ํ…Œ์ŠคํŠธ ํ•„์š”.
(์ข…๋ฃŒ์ผ์ด ์‹œ์ž‘์ผ ์ด์ „์ผ ๋•Œ ๋“ฑ)
isRepeatStructureChanged โญโญโญโญ (์ค‘์š”) ๊ธฐ์กด ์ผ์ • ์ˆ˜์ • ์‹œ ๋ฐ˜๋ณต ๊ทธ๋ฃน ์žฌ์ƒ์„ฑ ์—ฌ๋ถ€ ํŒ๋‹จ → ์ž˜๋ชป๋˜๋ฉด ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๊ทธ๋ฃน ๊นจ์ง€๊ฑฐ๋‚˜ ์—…๋ฐ์ดํŠธ ๋ˆ„๋ฝ๋จ.
copyScheduleProperties โญ (๋‚ฎ์Œ) ๋‹จ์ˆœ ์†์„ฑ ๋ณต์‚ฌ. ์ผ๋ฐ˜์ ์œผ๋กœ getter/setter ๋‹จ์œ„๋Š” ํ…Œ์ŠคํŠธ ๊ฐ€์น˜ ๋‚ฎ์Œ. ๋Œ€์‹  ์Šคํ”„๋ง BeanUtils ๊ฐ™์€ ํ‘œ์ค€ ์œ ํ‹ธ ์“ฐ๋ฉด ์•„์˜ˆ ํ…Œ์ŠคํŠธ ํ•„์š” ์—†์Œ.

๐Ÿ“– ์˜ˆ์‹œ (Reflection ์‚ฌ์šฉ)

@Test
void testGenerateRepeatedSchedules() throws Exception {
    Method method = ScheduleService.class.getDeclaredMethod("generateRepeatedSchedules", ScheduleDTO.class);
    method.setAccessible(true);
    
    ScheduleDTO schedule = new ScheduleDTO();
    
    // schedule ์ดˆ๊ธฐํ™”
    List<ScheduleDTO> result = (List<ScheduleDTO>) method.invoke(scheduleService, schedule);

    assertNotNull(result);
    assertFalse(result.isEmpty());
}

 

๐Ÿ“Œ 4. DB ์—ฐ๋™ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ

โœ… 4-1. ๋ชฉ์ 

  • DB ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”๊พธ๋Š” ํ•จ์ˆ˜ ์ •ํ™•์„ฑ ํ™•์ธ
  • ์‹ค์ œ ํ˜ธ์ถœ ๊ฒฝ๋กœ ํ…Œ์ŠคํŠธ → ์ปค๋ฒ„๋ฆฌ์ง€ ํ™•๋ณด
  • ์šด์˜ DB ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๊ณ  ์•ˆ์ „ํ•˜๊ฒŒ ํ…Œ์ŠคํŠธ

โœ… 4-2. ๋ฐฉ๋ฒ•

  • ์ธ๋ฉ”๋ชจ๋ฆฌ DB ์‚ฌ์šฉ (H2 ๋“ฑ)
  • ํŠธ๋žœ์žญ์…˜ ๋กค๋ฐฑ
@Transactional
@Test
void testDBUpdate() { ... }

 

  • Mocking (DB ํ˜ธ์ถœ ์ž์ฒด๋ฅผ Mock)

 

๐Ÿ“Œ 5. ํ…Œ์ŠคํŠธ ์‹คํ–‰

โœ…  # ํด๋ž˜์Šค ์ „์ฒด ์‹คํ–‰

mvn test

mvn test -Dtest=ScheduleServiceTest (test๊นŒ์ง€๋งŒ ์ณ๋„ ๋””ํดํŠธ)

 

โœ…  # ํŠน์ • ๋ฉ”์„œ๋“œ๋งŒ ์‹คํ–‰

mvn test -Dtest=ScheduleServiceTest#testUpdateSchedule_RepeatStructureChanged

 

๐Ÿ“Œ 6. ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€๋ž€?

  • ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์‹œ ์ฝ”๋“œ๊ฐ€ ์–ผ๋งˆ๋‚˜ ์‹คํ–‰๋˜์—ˆ๋Š”์ง€ ๋น„์œจ๋กœ ๋‚˜ํƒ€๋‚ธ ์ง€ํ‘œ

โœ… ์ข…๋ฅ˜

  • ๋ผ์ธ(Line) ์ปค๋ฒ„๋ฆฌ์ง€: ๋ช‡ ์ค„ ์‹คํ–‰๋๋Š”์ง€
  • ๋ธŒ๋žœ์น˜(Branch) ์ปค๋ฒ„๋ฆฌ์ง€: if/else, switch ๋ถ„๊ธฐ ์‹คํ–‰ ์—ฌ๋ถ€
  • ์กฐ๊ฑด(Condition) ์ปค๋ฒ„๋ฆฌ์ง€: ์กฐ๊ฑด์‹ true/false ์ผ€์ด์Šค

๐Ÿ’ก ์‹ค์ œ ํ˜„์—…์—์„œ๋Š”?

  1. ์ปค๋ฒ„๋ฆฌ์ง€ ์ธก์ • ๋„๊ตฌ (JaCoCo, IntelliJ built-in, SonarQube) ๋กœ ๋ฆฌํฌํŠธ๋ฅผ ์ƒ์„ฑ
  2. CI/CD ํŒŒ์ดํ”„๋ผ์ธ์— ์ปค๋ฒ„๋ฆฌ์ง€ ๊ฒ€์ฆ์„ ๊ฑธ์–ด๋‘ 
  3. ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง, ์˜ˆ์™ธ ์ฒ˜๋ฆฌ, ๋ถ„๊ธฐ๋ฅผ ์ค‘์ ์ ์œผ๋กœ ํ…Œ์ŠคํŠธ
  4. ์ปค๋ฒ„๋ฆฌ์ง€ ์ˆ˜์น˜๋ณด๋‹ค๋Š” ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์˜ ์‹œ๋‚˜๋ฆฌ์˜ค ์™„์„ฑ๋„๋ฅผ ๋” ์ค‘์‹œ (ํ…Œ์ŠคํŠธ๊ฐ€ ์ง„์งœ ๋ฒ„๊ทธ๋ฅผ ์žก์„ ์ˆ˜ ์žˆ๋‚˜?)

 

๐Ÿ“Œ 7. ์ปค๋ฒ„๋ฆฌ์ง€ ์ธก์ • ๋ฐฉ๋ฒ• - JaCoCo

๐Ÿ” 7-1. JaCoCo ์—์ด์ „ํŠธ๋ž€?

  • JVM์—์„œ ์‹คํ–‰๋˜๋Š” ์ž‘์€ ํ”„๋กœ๊ทธ๋žจ(JAR)
  • Java ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹คํ–‰๋˜๋Š” ๋™์•ˆ ์–ด๋–ค ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋๋Š”์ง€ ์ถ”์ (track)ํ•˜๋Š” ์—ญํ• 
  • ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์‹œ ์ฝ”๋“œ ์‹คํ–‰ ์ •๋ณด ๊ธฐ๋ก → jacoco.exec ์ƒ์„ฑ → ๋ฆฌํฌํŠธ ๋ณ€ํ™˜ ๊ฐ€๋Šฅ

 

โ“์–ด๋–ป๊ฒŒ ๋™์ž‘ํ• ๊นŒ?

1๏ธโƒฃ Maven์—์„œ jacoco-maven-plugin์ด prepare-agent ๋ชฉํ‘œ(goal)๋ฅผ ์‹คํ–‰

2๏ธโƒฃ JVM ์˜ต์…˜์— ์ž๋™์œผ๋กœ ๋ถ™์Œ: -javaagent:"path/to/jacocoagent.jar=destfile=target/jacoco.exec"

3๏ธโƒฃ JVM์ด ์ฝ”๋“œ ์‹คํ–‰ํ•  ๋•Œ ๋ชจ๋“  ํด๋ž˜์Šค์˜ ์‹คํ–‰ ์ •๋ณด๋ฅผ ์—์ด์ „ํŠธ๊ฐ€ ๊ฐ€๋กœ์ฑ„์„œ ๊ธฐ๋ก

4๏ธโƒฃ ํ…Œ์ŠคํŠธ ์ข…๋ฃŒ ํ›„ jacoco.exec ํŒŒ์ผ์— ๊ธฐ๋ก ์ €์žฅ

5๏ธโƒฃ jacoco:report ์‹คํ–‰ํ•˜๋ฉด .exec ํŒŒ์ผ → HTML/CSV ๋“ฑ ๋ฆฌํฌํŠธ๋กœ ๋ณ€ํ™˜

โœ… 7-2. ์„ค์ • ๋ฐฉ๋ฒ•

๐Ÿ“– Maven ์„ค์ •

<plugin>
  <groupId>org.jacoco</groupId>
  <artifactId>jacoco-maven-plugin</artifactId>
  <version>0.8.8</version>
  <executions>
    <execution>
      <goals><goal>prepare-agent</goal></goals>
    </execution>
    <execution>
      <id>report</id>
      <phase>verify</phase>
      <goals><goal>report</goal></goals>
    </execution>
  </executions>
</plugin>

 

โœ… 7-3. ์‹คํ–‰ ๋ฐฉ๋ฒ•

๐Ÿ”น๋‹จ์ˆœ ํ…Œ์ŠคํŠธ + ์ปค๋ฒ„๋ฆฌ์ง€ ํ™•์ธ: mvn clean test jacoco:report

๐Ÿ”นํ…Œ์ŠคํŠธ + ํ™•์žฅ ๊ฒ€์ฆ: mvn clean test verify

  • clean → target/ ํด๋” ์‚ญ์ œ
  • test → src/test/java ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์‹คํ–‰
  • jacoco:report → ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ๊ธฐ๋ฐ˜์œผ๋กœ JaCoCo ์ปค๋ฒ„๋ฆฌ์ง€ ๋ฆฌํฌํŠธ ์ƒ์„ฑ
  • verify → ํ…Œ์ŠคํŠธ ํ›„ ์ถ”๊ฐ€ ๊ฒ€์ฆ ๋‹จ๊ณ„ ์‹คํ–‰

 

๐Ÿšจ 8. ์‹คํ–‰ ์˜ค๋ฅ˜ - ํ•œ๊ธ€ ๊ฒฝ๋กœ, jdk๋ฒ„์ „ (CMD์—์„œ ์‹คํ–‰)

โœ… 8-1. ๊ฒฝ๋กœ ๋ฌธ์ œ

IDE๋‚ด์—์„œ ์‹คํ–‰ ์‹œ ํ•œ๊ธ€ ๊ฒฝ๋กœ ์ธ์‹ ๋ชป ํ•จ.

๐Ÿ”น ์›์ธ

IDE ๋‹จ์œ„ ์‹คํ–‰ → Maven ๊ฐœ์ž… X → JaCoCo ์˜ต์…˜ ์ ์šฉ ์•ˆ ๋จ → .exec ํŒŒ์ผ ์ƒ์„ฑ ์‹คํŒจ

๐Ÿ”น ํ•ด๊ฒฐ

  1. Maven Surefire ์ž„์‹œ๊ฒฝ๋กœ ์ง€์ •: argLine + JaCoCo destFile ์ง€์ •
  2. ์ž„์‹œ ํด๋” ์˜๋ฌธ ๊ฒฝ๋กœ ์ƒ์„ฑ ๋ฐ Cmd์—์„œ ์‹คํ–‰

1๏ธโƒฃ pom.xml ์„ค์ •

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>3.1.2</version>
  <configuration>
    <argLine>-javaagent:C:/Temp/.m2/org/jacoco/org.jacoco.agent/0.8.8/org.jacoco.agent-0.8.8-runtime.jar=destfile=C:/Temp/TestMvn/jacoco.exec</argLine>
  </configuration>
</plugin>

<plugin>
  <groupId>org.jacoco</groupId>
  <artifactId>jacoco-maven-plugin</artifactId>
  <version>0.8.8</version>
  <configuration>
    <destFile>C:/Temp/jacoco.exec</destFile>
    <dataFile>C:/Temp/jacoco.exec</dataFile>
    <outputDirectory>${project.build.directory}/jacoco-report</outputDirectory>                    
  </configuration>
</plugin>

 

2๏ธโƒฃ ์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ ์„ธํŒ… ๋ฐ cmd ๋ช…๋ น

  • ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ ๊ฒฝ๋กœ์—์„œ ์‹คํ–‰
set MAVEN_OPTS=-Dmaven.repo.local=C:\Temp\.m2
set JAVA_TOOL_OPTIONS=-Duser.home=C:\Temp\javahome
mvn clean test jacoco:report

โœ”๏ธ cmd ๋‹ซ์œผ๋ฉด ์‚ฌ๋ผ์ง€๋Š” ์ž„์‹œ ์„ค์ •

โŒ ๋‹จ์ : ๊ธฐ์กด .m2 ์บ์‹œ๋Š” ๋ชป ์“ฐ๊ณ , ์ž„์‹œ ํด๋”์— ์ƒˆ๋กœ ๋‹ค์šด๋กœ๋“œ๋จ (์ปค์Šคํ…€๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ?๋Š” ์ง์ ‘ ์ƒˆ๋กœ ์ƒ์„ฑํ•œ .m2 ๋ฐ‘์— ๋ณต์‚ฌํ•ด์•ผํ•จ. ํŒŒ์ผ๋ช…๋„ ๋ฒ„์ „ ๋ถ™์—ฌ์„œ!!

  • ์ปค์Šคํ…€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์ •: C:\Temp\.m2\local\lib\MessageCalendarAseUtil\1.0\MessageCalendarAseUtil-1.0.jar
    <!-- MessageCalendarAseUtil -->
    <dependency>
        <groupId>local.lib</groupId>
        <artifactId>MessageCalendarAseUtil</artifactId>
        <version>1.0</version>
    </dependency>

 

โœ… 7-2. JDK ๋ฒ„์ „ ๋ฌธ์ œ

PC์— jdk8, jdk17 ์žˆ๊ณ  ํ”„๋กœ์ ํŠธ๋งˆ๋‹ค ๋‹ค๋ฅธ ๋ฒ„์ „ ์ด์šฉ. ๊ธฐ๋ณธ 8.

ํ•ด๋‹น ํ”„๋กœ์ ํŠธ๋Š” IDE๋‚ด์—์„œ 17์‚ฌ์šฉํ•˜๋„๋ก ์„ค์ •ํ•ด์„œ ์ด์šฉํ–ˆ์—ˆ์Œ.

 

๐Ÿ”น์›์ธ

  • ๋ฉ”์ด๋ธ JDK ๋ฒ„์ „ 
  • mvn -v  ์ž…๋ ฅ ์‹œ ์ถœ๋ ฅ

Apache Maven 3.9.9 (8e8579a9e76f7d015ee5ec7bfcdc97d260186937)

Maven home: C:\Program Files\Apache\Maven\apache-maven-3.9.9

Java version: 1.8.0_202, vendor: Oracle Corporation, runtime: C:\Program Files\Java\jdk1.8.0_202\jre

Default locale: ko_KR, platform encoding: MS949

OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"


๐Ÿ”นํ•ด๊ฒฐ

set JAVA_HOME=C:\Program Files\Java\jdk-17
set PATH=%JAVA_HOME%\bin;%PATH%

 

๐Ÿ”น์ˆ˜์ • ํ›„

  • mvn -v ์ž…๋ ฅ ์‹œ ์ถœ๋ ฅ

Picked up JAVA_TOOL_OPTIONS: -Duser.home=C:\Temp\TestMvn\javahome

Apache Maven 3.9.9 (8e8579a9e76f7d015ee5ec7bfcdc97d260186937)

Maven home: C:\Program Files\Apache\Maven\apache-maven-3.9.9

Java version: 17.0.13, vendor: Oracle Corporation, runtime: C:\Program Files\Java\jdk-17

Default locale: ko_KR, platform encoding: MS949

OS name: "windows 11", version: "10.0", arch: "amd64", family: "windows"

 

๐Ÿ“Œ8. ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ

โœ… 8-1. ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ํ™•์ธ

๐Ÿ”นํŒŒ์ผ๋ณ„ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ + ์ „์ฒด ๊ฒฐ๊ณผ

[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 15.97 s -- in kr.co.example.calender.controller.ScheduleRestControllerTest

[INFO] Running kr.co. example.calender.service.ScheduleServicePrivateTest

[INFO] Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.395 s -- in kr.co.example.calender.service.ScheduleServicePrivateTest

[INFO] Running kr.co.example.calender.service.ScheduleServiceTest

[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.052 s -- in kr.co.example.calender.service.ScheduleServiceTest

[INFO]

[INFO] Results:

[INFO]

[INFO] Tests run: 22, Failures: 0, Errors: 0, Skipped: 0

[INFO]

[INFO]

[INFO] --- jacoco:0.8.8:report (default-cli) @ calendar ---

[INFO] Loading execution data file C:\Temp\TestMvn\jacoco.exec

[INFO] Analyzed bundle 'calendar' with 48 classes

[INFO] ------------------------------------------------------------------------

[INFO] BUILD SUCCESS

[INFO] ------------------------------------------------------------------------

[INFO] Total time:  01:14 min

[INFO] Finished at: 2025-08-18T11:27:50+09:00

[INFO] ------------------------------------------------------------------------

 

 

โœ… 8-2. ์ปค๋ฒ„๋ฆฌ์ง€ ๋ฆฌํฌํŠธ ํ™•์ธ

๐Ÿ”น ์ƒ์„ฑ์œ„์น˜: Calendar\target\jacoco-report\index.html

๐Ÿ”น ๋ธŒ๋ผ์šฐ์ €์—์„œ ์˜คํ”ˆ

๋””๋ ‰ํ† ๋ฆฌ -> ํด๋ž˜์Šค -> ๋ฉ”์„œ๋“œ -> ์ฝ”๋“œ ๋ผ์ธ ์‹คํ–‰ ์œ ๋ฌด ํ™•์ธ ๊ฐ€๋Šฅ

 

๐Ÿš€ ํ›„๊ธฐ

๐Ÿ”น ์ฃผ์š” ๋กœ์ง ํ…Œ์ŠคํŠธ

์ฃผ์š” ๋กœ์ง์€ ScheduleService์ด์–ด์„œ, ์ด ์„œ๋น„์Šค ์œ„์ฃผ๋กœ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค.

 

๐Ÿ”น ์กฐ๊ฑด๋ณ„ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ๊ณผ ์˜ค๋ฅ˜ ๊ฒฝํ—˜

์œ„์—์„œ JUnit์— ๋Œ€ํ•ด ๊ณต๋ถ€ํ•˜๋ฉฐ ๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ๋งŒ์œผ๋กœ ํ…Œ์ŠคํŠธํ–ˆ์„ ๋•Œ ์„œ๋น„์Šค ์ปค๋ฒ„๋ฆฌ์ง€๋Š” ์•ฝ 5%์˜€๋Š”๋ฐ, ์ถ”๊ฐ€๋กœ ์กฐ๊ฑด๋ฌธ ์ผ€์ด์Šค๋ณ„๋กœ ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ•ด๋ดค๋‹ค.


์˜ˆ๋ฅผ ๋“ค์–ด, "์ผ์ • ์ˆ˜์ • ํ•จ์ˆ˜ - ๋ฐ˜๋ณต ์ผ์ • ์ˆ˜์ •" ์ผ€์ด์Šค๋ฅผ ํ…Œ์ŠคํŠธ๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑ.

   @Test
    @DisplayName("์ผ์ • ์ˆ˜์ • ์„ฑ๊ณต ํ…Œ์ŠคํŠธ - ๋ฐ˜๋ณต ๊ตฌ์กฐ ๋ณ€๊ฒฝ ์‹œ ์‚ญ์ œ ํ›„ ์žฌ์ƒ์„ฑ")
    void testUpdateSchedule_RepeatStructureChanged() {
        // Given
        ScheduleDTO updateSchedule = new ScheduleDTO();
        BeanUtils.copyProperties(testSchedule, updateSchedule); // Spring BeanUtils ์‚ฌ์šฉ
        updateSchedule.setSchdTitle("์ˆ˜์ •๋œ ์ผ์ •");
        updateSchedule.setModifierId("USER001");
        updateSchedule.setUpdateMode("CURRENT");

        // ๋ฐ˜๋ณต ๊ตฌ์กฐ ๋ณ€๊ฒฝ
        updateSchedule.setReptTypeCd("WEEKLY");
        updateSchedule.setBeginDate("2024-02-15");
        updateSchedule.setReptFinDate("2024-03-10");

        when(scheduleMapper.selectSchedule("SCHD001"))
            .thenReturn(testSchedule);
        when(scheduleMapper.selectScheduleWithSharedUsers("SCHD001"))
            .thenReturn(testSchedule);
        // when(scheduleMapper.deleteScheduleSharedUsers("SCHD001"))
        //     .thenReturn(1);
        when(scheduleMapper.deleteSchedule("SCHD001"))
            .thenReturn(1);
        when(userService.svcSelectCompnDept("USER001"))
            .thenReturn(testUser);
        when(scheduleMapper.insertSchedule(any(ScheduleDTO.class)))
            .thenReturn(1);

        // When
        // try (MockedStatic<SecurityUtil> mockedSecurityUtil = mockStatic(SecurityUtil.class)) {
        //     mockedSecurityUtil.when(SecurityUtil::getAuthUserId).thenReturn("USER001");

            int result = scheduleService.svcUpdateSchedule(updateSchedule);

            // Then
            assertAll(
                () -> assertEquals(1, result),
                () -> verify(scheduleMapper).selectSchedule("SCHD001"),
                () -> verify(scheduleMapper).selectScheduleWithSharedUsers("SCHD001"),
                // () -> verify(scheduleMapper).deleteScheduleSharedUsers("SCHD001"),
                () -> verify(scheduleMapper).deleteSchedule("SCHD001"),
                () -> verify(userService).svcSelectCompnDept("USER001"),
                () -> verify(scheduleMapper).insertSchedule(any(ScheduleDTO.class))
            );
        // }

    }

 

ํ•˜๋ฉด์„œ ๋กœ์ง ๊ฒ€์ฆ์ด ๊ฐ€๋Šฅํ–ˆ๋‹ค.
 

deleteScheduleSharedUsers์ด๊ฑฐ ๋ฐ”๋กœ ์‹คํ–‰๋˜๋Š” ์ค„ ์•Œ์•˜๋Š”๋ฐ ์•„๋ž˜ ์˜ค๋ฅ˜๋– ์„œ ๋ณด๋‹ˆ selectScheduleWithSharedUsers๊ฒฐ๊ณผ ์žˆ์„๋•Œ๋งŒ ์‹คํ–‰.

*Wanted but not invoked: ํ…Œ์ŠคํŠธ์—์„œ verifyํ•œ ๋ฉ”์„œ๋“œ๋“ค์ด ํ˜ธ์ถœ๋˜์ง€ ์•Š์Œ.

org.mockito.exceptions.verification.WantedButNotInvoked: Wanted but not invoked: scheduleMapper.deleteScheduleSharedUsers( "SCHD001" );

 

์ด์ฒ˜๋Ÿผ Mockito์˜ verify๋ฅผ ํ†ตํ•ด ์„œ๋น„์Šค ๋ฉ”์„œ๋“œ ๋‚ด ํ˜ธ์ถœ ์ˆœ์„œ์™€ ์กฐ๊ฑด์„ ํ™•์ธ๊ฐ€๋Šฅ ํ–ˆ๊ณ , ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๋‹จ์ˆœ ๊ฒ€์ฆ์šฉ๋ฟ ์•„๋‹ˆ๋ผ ๋ฉ”์„œ๋“œ ์‹คํ–‰ ํ๋ฆ„ ํ™•์ธ์šฉ์œผ๋กœ๋„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค.

 

๐Ÿ”น  ์ปค๋ฒ„๋ฆฌ์ง€์™€ private ๋ฉ”์„œ๋“œ ํ…Œ์ŠคํŠธ
์กฐ๊ฑด๋ณ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ–ˆ์„ ๋•Œ ์„œ๋น„์Šค ์ปค๋ฒ„๋ฆฌ์ง€๋Š” 18%๊นŒ์ง€ ์˜ฌ๋ผ๊ฐ”๊ณ , ScheduleService ๋‚ด ํ•ต์‹ฌ ๋กœ์ง์€ 50% ์ด์ƒ ์ปค๋ฒ„ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. ๋˜ํ•œ private ๋ฉ”์„œ๋“œ๋„ public ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ๊ฐ„์ ‘์ ์œผ๋กœ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฐ€๋Šฅํ–ˆ๋‹ค.

 

๐Ÿ”น  ๋‚˜๋จธ์ง€ 50%์—๋Š” ์‚ฌ์šฉ์ž ๋ฉ”์ผ/์•Œ๋ฆผ(์—ฐ๋™์„œ๋น„์Šค) ๋“ฑ์ด ํฌํ•จ๋˜์–ด์„œ ์—ฌ๊ธฐ๊นŒ์ง€๋งŒ ํ™œ์šฉํ•ด๋ด„.