공공부하자개발 · 영어 학습 노트
자바
실무 확장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정리‹ 이전다음 ›

2. 핵심 원리

2.1 테스트가 필요한 진짜 이유

테스트는 세 가지를 줍니다. 코드를 고칠 때의 안전망(회귀를 바로 잡아냄), 설계에 대한 피드백(테스트하기 어려우면 결합이 높다는 신호), 그리고 실행 가능한 명세(주석보다 코드가 거짓말을 안 함)입니다. "찍어 보기" 는 이 세 가지를 하나도 못 줍니다.

항목 main 으로 찍어보기 JUnit 테스트
판정 사람이 눈으로 자동, assert
반복 실행 매번 손으로 다시 명령 한 줄
격리 DB·시계가 매번 다름 페이크·고정 시계
실패 위치 로그를 뒤져야 함 실패한 줄·값 표시

2.2 테스트 피라미드와 두 종류

종류 대상 실행 시간 무엇을 잡나
단위 클래스 하나, 의존은 가짜 밀리초 로직·경계값 오류
통합 진짜 DB·매퍼·SQL 수백 ms~초 SQL·스키마 불일치
E2E 배포된 전체 시스템 초~분 배선·설정 오류

단위 테스트가 압도적으로 많고, 통합 테스트는 핵심 경로만, E2E 는 소수만 둡니다. 이 레슨의 OrderServiceTest 가 단위, JdbcOrderRepositoryTest 가 통합에 해당합니다.

2.3 테스트 가능한 설계

OrderService 는 저장소·결제·시계 세 의존을 전부 생성자로 받습니다. 메서드 안에서 new JdbcOrderRepository() 나 LocalDateTime.now() 를 직접 부르면 그 순간 테스트가 불가능해집니다. 실행할 때마다 결과가 달라지고 진짜 DB 없이는 돌릴 수 없기 때문입니다.

OrderRepository·PaymentGateway 인터페이스는 "바꿔 끼우기 위해" 존재합니다. 운영에서는 JdbcOrderRepository, 테스트에서는 메모리 구현을 꽂습니다. Spring 의 @Autowired 생성자 주입도 같은 원리입니다.

시계는 Clock 을 빈으로 등록하고 실제로는 Clock.systemDefaultZone(), 테스트에서는 Clock.fixed(...) 를 씁니다. LocalDateTime.now() 를 직접 부르지 않고 LocalDateTime.now(clock) 으로 감싸는 것이 핵심입니다.

2.4 테스트 더블

종류 무엇을 하나 언제 쓰나
더미 값만 채움, 동작 없음 생성자 인자를 채우기만 할 때
스텁 정해진 답을 돌려줌 특정 응답을 강제할 때
페이크 실제 동작을 간단히 재구현 저장소처럼 상태를 가진 의존
목 호출 여부·인자를 검증 "호출됐는지" 자체가 검증 대상일 때

InMemoryOrderRepository 는 페이크, StubGateway 는 스텁입니다. 둘 다 손으로 8~10줄이면 충분해서 라이브러리가 필요 없습니다.

Mockito 를 쓰면 같은 스텁을 아래처럼 씁니다.

java
@Mock PaymentGateway pg;
@InjectMocks OrderService service;

when(pg.charge(any(), any())).thenThrow(new PaymentGateway.PaymentException("한도 초과"));
verify(pg, never()).charge(eq("kim"), any());   // 호출 안 됐는지 검증

손으로 쓴 페이크는 읽기 쉽고 다른 테스트에서도 재사용됩니다. Mockito 는 호출 여부·횟수·인자를 검증해야 하거나 인터페이스가 커서 손으로 다 구현하기 번거로울 때 낫습니다.

2.5 좋은 테스트의 모양

테스트 하나는 준비(Arrange)·실행(Act)·검증(Assert) 세 덩어리로 나뉩니다. 이 순서를 지키면 테스트가 무엇을 확인하는지 코드만 보고도 알 수 있습니다. 테스트 하나에는 검증할 이유가 하나여야 합니다.

이름은 상황_기대 형식이나 @DisplayName 한글 문장으로 씁니다. place_ok 보다 place_paymentFails_savesFailed 처럼 상황이 드러나야 실패 메시지만 보고도 원인을 짐작할 수 있습니다.

경계값(99999/100000/100001)은 @ParameterizedTest 와 @CsvSource 로 표처럼 나열합니다. 여러 필드를 한 번에 검증할 때는 assertAll 을 써서 하나가 깨져도 나머지 결과까지 다 봅니다. assertThrows 는 예외 객체를 돌려주므로 메시지까지 검증할 수 있습니다.

테스트 간에는 상태를 공유하면 안 됩니다. @BeforeEach 로 매번 새 페이크·스텁·서비스를 만들어야 어떤 순서로 실행되든 결과가 같습니다.

2.6 시간·난수·외부 세계 제어

Clock.fixed(instant, zone) 은 특정 순간에 멈춘 시계를 만듭니다. Clock.offset(clock, duration) 은 기존 시계를 앞뒤로 밀어, 이 레슨의 취소 테스트에서 "24시간 경과" 를 실제로 24시간 기다리지 않고 시뮬레이션합니다.

같은 원리로 난수는 Random 을 주입받게 만들고, 파일은 @TempDir 로 매 테스트마다 새 임시 폴더를 받습니다. 외부 HTTP 는 REST 레슨의 FakePartner 처럼 로컬 HttpServer 를 띄워 대체합니다. 세 가지 모두 "밖에서 주입 가능하게 만들기" 라는 같은 해법입니다.

2.7 DB 통합 테스트

JdbcOrderRepositoryTest 는 H2 메모리 DB 에 진짜 SQL 을 던집니다. 클래스당 커넥션·스키마는 @BeforeAll 에서 한 번만 만듭니다. 테스트마다 @BeforeEach 에서 트랜잭션을 시작하고 @AfterEach 에서 롤백해, 테스트가 남긴 행이 다음 테스트에 보이지 않게 합니다.

H2 와 Oracle 은 문법이 다릅니다. H2 의 MODE=Oracle 옵션으로 상당수는 맞출 수 있지만, 시퀀스·MERGE·ROWNUM 은 06 레슨에서 다뤘듯 방언 차이가 남아 실제 Oracle 로 최종 확인이 필요합니다.

Spring 은 @MybatisTest/@JdbcTest 에 @Transactional 을 붙이면 롤백을 자동으로 해 줍니다. @SpringBootTest 는 컨텍스트 전체를 띄워 느리므로 꼭 필요한 곳에만 씁니다.

2.8 실행과 운영

이 레슨은 junit-platform-console-standalone jar 하나로 Gradle 없이 테스트를 실행합니다. 실무 Gradle 프로젝트에서는 ./gradlew test 가 내부적으로 같은 콘솔 런처를 호출하고, 결과는 build/reports/tests 에 HTML 로 남습니다.

CI 에서는 테스트가 실패하면 배포를 멈춥니다. 커버리지(JaCoCo)는 "코드 중 안 본 곳을 찾는 도구" 일 뿐, 80% 같은 숫자를 채우는 것 자체가 목표가 되면 안 됩니다. 실행이 느린 테스트는 @Tag("slow") 로 분리해 평소에는 빼고 돌립니다.

powershell
.\gradlew test                              # build\reports\tests\test\index.html 에 결과
.\gradlew test jacocoTestReport             # build\reports\jacoco 에 커버리지 리포트 추가
.\gradlew test -x test --tests "*Slow*"     # slow 태그만 빼고 돌리는 식으로 필터링
핵심 원리
  • 2.1 테스트가 필요한 진짜 이유
  • 2.2 테스트 피라미드와 두 종류
  • 2.3 테스트 가능한 설계
  • 2.4 테스트 더블
  • 2.5 좋은 테스트의 모양
  • 2.6 시간·난수·외부 세계 제어
  • 2.7 DB 통합 테스트
  • 2.8 실행과 운영
이전 섹션1 왜 배우는가2 / 7다음 섹션3 코드 예제