공공부하자개발 · 영어 학습 노트
자바
실무 확장Excel · 파일 업로드 · DB 연동0/22 완료
  • 01Excel(XLSX) 구조와 순수 JDK로 읽기/쓰기
  • 02Apache POI로 Excel 업로드/다운로드
  • 03파일 업로드/다운로드 서버 (HttpServer)
  • 04JDBC 기초와 트랜잭션 (H2)
  • 05MyBatis 어노테이션 매퍼로 쿼리 연동
  • 06MyBatis XML 매퍼 · Oracle 방언 · PageHelper · Spring Boot
  • 07REST API 서버와 JSON
  • 08Vue 3 SPA 와 Java 서버 연동
  • 09@Scheduled 운영
  • 10로깅 실무: 레벨·계층, MDC 추적, 예외·성능, 마스킹, 롤링, JSON 로그
  • 11외부 API 연동
  • 12테스트 실무
  • 13암호화·개인정보 보호
  • 14인코딩·한글 실무
  • 15@Transactional 심화
  • 16긴 작업 비동기 처리와 진행률
  • 17SFTP·FTP 파일 연계
  • 18로컬 캐시와 @Cacheable
  • 19메일·알림 발송
  • 20웹 보안 체크리스트
  • 21빌드 도구와 폐쇄망 의존성 반입
  • 22성능 측정: p50·p95·p99, 측정 계층, JFR, JMH 함정, 자체 부하 테스트, 병목 순위
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › 실무 확장 › 12 / 22

테스트 실무

JUnit 5 단위·통합 테스트, 테스트 더블, 시계 제어, H2 롤백
섹션 7진행 0 / 22
1왜 배우는가2핵심 원리3코드 예제4응용 변형 예제5자주 하는 실수 (Tip)6연습 문제7정리‹ 이전다음 ›

4. 응용 변형 예제

4.1 Spring Boot 로 옮기기 — Mockito

java
@ExtendWith(MockitoExtension.class)
class OrderServiceMockitoTest {

    @Mock OrderRepository repo;
    @Mock PaymentGateway pg;
    @InjectMocks OrderService service;   // repo, pg 를 자동으로 주입

    @Test
    void 결제_실패시_FAILED_저장() {
        when(repo.save(any())).thenAnswer(inv -> inv.getArgument(0));   // 넣은 그대로 돌려줌
        when(pg.charge(any(), any())).thenThrow(new PaymentGateway.PaymentException("한도 초과"));

        assertThrows(PaymentGateway.PaymentException.class,
                () -> service.place("kim", new BigDecimal("50000")));

        verify(repo).save(argThat(o -> "FAILED".equals(o.status())));   // 정확히 FAILED 로 저장됐는지
    }
}

@TestConfiguration
class ClockTestConfig {
    @Bean
    Clock clock() { return Clock.fixed(Instant.parse("2026-09-10T05:00:00Z"), ZoneId.of("Asia/Seoul")); }
}

운영 빈은 @Bean Clock clock() { return Clock.systemDefaultZone(); } 로 등록하고, 테스트에서는 @TestConfiguration 으로 고정 시계를 덮어씁니다.

verify(repo).save(argThat(...)) 는 "정확히 이 조건을 만족하는 인자로 한 번 불렸는가" 를 확인합니다. 호출 자체가 검증 대상일 때만 이렇게 쓰고, 결과값을 확인할 수 있으면 상태 검증을 우선합니다.

4.2 MyBatis 매퍼 통합 테스트

java
@MybatisTest
@AutoConfigureTestDatabase(replace = Replace.NONE)   // H2 대신 지정한 DataSource 그대로 사용
@Sql("classpath:schema.sql")
class OrderMapperTest {

    @Autowired OrderMapper mapper;

    @Test
    void 상태_필터가_없으면_전체() {
        assertEquals(3, mapper.findByCustomer("kim", null).size());
    }

    @Test
    void 상태_필터를_주면_해당_상태만() {
        // <if test="status != null"> 분기를 이 케이스가 검증한다
        assertEquals(1, mapper.findByCustomer("kim", "PAID").size());
    }

    @Test
    void 고객이_없으면_빈_리스트() {
        assertTrue(mapper.findByCustomer("nobody", null).isEmpty());
    }
}

매퍼 XML 의 동적 SQL 분기(<if>, <choose>) 는 분기마다 최소 한 테스트를 둬야 실제로 그 SQL 이 실행됐는지 확인됩니다.

schema.sql 은 JdbcOrderRepository.DDL 과 같은 역할을 합니다. 스키마를 파일로 빼 두면 여러 매퍼 테스트 클래스가 같은 스키마를 재사용할 수 있습니다.

4.3 REST 핸들러 테스트

java
class OrderApiTest {
    static HttpServer server;
    static HttpClient client = HttpClient.newHttpClient();

    @BeforeAll
    static void start() throws IOException {
        server = HttpServer.create(new InetSocketAddress(0), 0);   // 포트 0 = 임의 포트
        server.createContext("/orders", new OrderHandler(service));
        server.start();
    }

    @AfterAll
    static void stop() { server.stop(0); }

    @Test
    void 존재하지_않는_주문은_404() throws Exception {
        var req = HttpRequest.newBuilder(URI.create("http://localhost:" + server.getAddress().getPort() + "/orders/999")).build();
        var res = client.send(req, HttpResponse.BodyHandlers.ofString());
        assertEquals(404, res.statusCode());
    }
}

Spring MockMvc 로는 서버를 안 띄우고도 같은 검증을 합니다.

java
@WebMvcTest(OrderController.class)
class OrderControllerTest {
    @Autowired MockMvc mvc;
    @MockBean OrderService service;

    @Test
    void 없는_주문은_404() throws Exception {
        mvc.perform(get("/orders/999")).andExpect(status().isNotFound());
    }
}

4.4 회귀 테스트 만들기

운영에서 "25만 5원 주문의 할인이 25001원으로 나왔다" 는 장애가 들어왔다고 합니다. 원 단위 절사가 안 된 버그입니다.

java
@Test
@DisplayName("BUG-1042: 할인은 원 단위로 절사돼야 한다")
void discount_truncatesToWon() {
    // 이 테스트를 먼저 작성하면 수정 전에는 실패한다
    assertEquals(new BigDecimal("25000"), OrderService.discountFor(new BigDecimal("250005")));
}

버그 번호를 테스트 이름에 남기면, 나중에 "이 케이스는 왜 있지" 라는 질문에 바로 답이 됩니다. 실패하는 테스트를 먼저 커밋하고, 수정 커밋에서 통과시키는 순서가 원인과 해결을 분리해 보여 줍니다.

이렇게 만든 회귀 테스트는 삭제하지 않고 그대로 남깁니다. 같은 버그가 나중에 다시 생기면 배포 전에 바로 잡아냅니다.

응용 변형 예제
  • 4.1 Spring Boot 로 옮기기 — Mockito
  • 4.2 MyBatis 매퍼 통합 테스트
  • 4.3 REST 핸들러 테스트
  • 4.4 회귀 테스트 만들기
이전 섹션3 코드 예제4 / 7다음 섹션5 자주 하는 실수 (Tip)