[Java] 테스트 코드 개념 정리하기

2025. 12. 1. 23:37·JAVA/Java 실습

시작하며...

항상 프로젝트와 과제를 수행함에 있어서 테스트 코드 작성에 익숙하지 않고, 어떻게 접근해야 하는지에 대한 궁금증만 가지고 교육과정을 진행하고 있었다.

 

오늘은 테스트코드의 필요성과 개념, 그리고 앞으로 프로젝트와 과제를 진행함에 있어서 꾸준히 활용해야 할 내용에 대해서 정리해보고자 한다.

 

테스트코드란?

: 작성한 코드를 실행했을 때 에러가 나거나, 의도한 대로 동작하지 않는 경우를 방어하기 위한 검증의 과정이라고 할 수 있다.

 

테스트 코드를 작성해야 하는 핵심적인 이유

  1. 안정성 확보 (Stability)
    • 새로운 기능을 추가하거나 기존 코드를 수정할 때, 기존 기능이 망가지지 않았는지(Regression) 즉시 확인할 수 있음
    • 수정으로 인한 추가적인 에러나 예외발생에 대한 두려움을 없앨 수 있음.
  2. 빠른 피드백 (Fast Feedback)
    • 서버를 띄우고, Postman으로 요청을 보내고, 로그를 확인하는 과정은 시간이 오래 걸림
    • 테스트 코드는 버튼 하나로 수 초 이내의 로직 정합성 검증을 진행해줌
  3. 문서화 (Documentation)
    • 테스트 코드는 그 자체로 살아있는 문서
    • 요청과 응답의 과정에 대한 의문이 있을 때 테스트 코드를 보면 정확한 예시를 알 수 있음
  4. 리팩토링의 자신감
    • 코드를 개선하고 싶을 때, 테스트가 있으면 기존 동작이 유지되는지 확인할 수 있음
    • 리팩토링 후 모든 테스트가 통과하면, 안심하고 변경사항을 커밋할 수 있음

테스트 코드 작성 시와 미작성 시 비교

항목 테스트 코드 작성 테스트 코드 미작성
초기 개발 비용 개발 시간과 비용 증가 빠르게 개발 가능
버그 수정 비용 초기에 발견된 버그를 빠르고 저렴하게 해결 가능 배포 후 발견 시 높은 비용 발생
품질 코드 변경 시 안정성을 보장 코드 변경 시 회귀 오류 발생 가능성 높음
유지보수 기능 문서화 및 코드 이해도 증가 신규 팀원이 코드 이해 및 적응하는데 시간 소요
배포 안정성 자동화 테스트로 배포 전 품질 보장 배포 후 오류 발견 시 롤백 및 긴급 수정 필요
장애 발생 확률 회귀 테스트를 통해 장애 가능성 최소화 장애 발생 가능성 증가

 

테스트의 종류

단위 테스트(Unit Test)

  • 작은 코드 단위를 독립적으로 검증하는 테스트
  • 소프트웨어의 가장 작은 단위인 모듈, 함수, 클래스 등의 개별적인 기능을 테스트하는 것
  • 주로 프로그래머가 작성하며, 코드의 동작을 검증하고 예상대로 동작하는지 확인한다.
  • 코드 변경 시에 기존 코드의 충돌 및 오류를 발견 가능하며, 새로운 코드 동작 성공여부를 확인할 수 있다.
  • TDD에서의 테스트 케이스는 주로 단위 테스트 작성을 의미한다. 주로 자동화되어 사용된다 (CI)

  • 장점
    • 실행 속도가 빠름
    • 특정 로직만 집중해서 테스트 가능
    • 외부 환경(DB, 네트워크)에 영향을 받지 않음
  • 단점
    • 실제 통합 환경에서 발생할 수 있는 문제를 발견하지 못할 수 있음
    • Mock 설정이 복잡할 수 있음

통합 테스트(Integration Test)

  • 단위 테스트에서 독립적인 기능을 확인했다면, 통합 테스트에서는 모듈 간의 연계된 동작을 확인한다.
  • 각각의 모듈이 제대로 상호작용하는지, 시스템의 구성 요소들이 올바르게 통합되어 동작하는지를 확인한다.

  • 장점
    • 실제 환경과 유사한 상황에서 테스트
    • 모듈 간 통합 문제를 발견할 수 있음
    • End-to-End 시나리오 검증 가능
  • 단점
    • 실제 환경과 유사한 상황에서 테스트
    • 모듈 간 통합 문제를 발견할 수 있음
    • 테스트 간 데이터 격리가 필요

부하 테스트(Stress Test)

  • 소프트웨어가 예상되는 최대 부하를 견딜 수 있는지를 검증한다.
  • 시스템에 과도한 부하를 가하여 응답 시간, 처리량, 자원 사용량 등을 측정하고, 성능 문제를 파악 및 최적화할 수 있다.

언제 무엇을 사용해야 하는가?

테스트 대상 추천하는 테스트 타입 이유
Repository @DataJpaTest (통합) JPA 쿼리 동작은 실제 DB로 확인
Service 단위 테스트
(@ExtendWith(MockitoExtension.class)
비즈니스 로직만 집중 테스트
Controller @WebMvcTest (단위) HTTP 요청/응답 처리만 테스트
전체 플로우 @SpringBootTest (통합) API 호출부터 DB 저장까지 전체 검증
일반 유틸 클래스 단위 테스트 의존성 없이 로직만 테스트

 

테스트 도구

: 프로젝트 내 기본 구성으로 테스트를 위한 도구들이 포함되어 있음

 

JUnit 5

자바 진영의 표준 테스트 프레임워크

주요 어노테이션

 

@Test

테스트 메서드임을 나타냄

@Test
void createBoard() {
    // 이 메서드가 테스트로 실행됩니다
}

 

@BeforeEach

각 테스트 메서드 실행 전에 실행되며, 테스트 데이터를 초기화할 때 사용

@BeforeEach
void setUp() {
    // 모든 테스트 전에 실행됨
    boardRepository.deleteAll();    userRepository.deleteAll();}

 

@AfterEach

각 테스트 메서드 실행 후에 실행되며, 테스트 후 정리 작업에 사용

@AfterEach
void tearDown() {
    // 모든 테스트 후에 실행됨
    boardRepository.deleteAll();}

 

@BeforeAll / @AfterAll

모든 테스트 실행 전/후에 한번만 실행되며, static 메서드여야 한다.

@BeforeAll
static void setUpAll() {
    // 모든 테스트 시작 전에 한 번만 실행
}

 

 

@DisplayName

테스트에 설명을 추가한다.

@Test
@DisplayName("게시글 생성 시 제목, 내용, 작성자가 올바르게 저장된다")
void createBoard() {
    // ...}

 

AssertJ

테스트 검증을 위한 라이브러리이며 직관적인 문법을 제공한다.

기본 검증

// 값 비교
assertThat(actual).isEqualTo(expected);

// null 검증
assertThat(object).isNotNull();
assertThat(object).isNull();

// boolean 검증
assertThat(result).isTrue();
assertThat(result).isFalse();

// 문자열 검증
assertThat(str).contains("테스트");
assertThat(str).startsWith("Bearer ");
assertThat(str).isNotEmpty();

// 숫자 검증
assertThat(number).isGreaterThan(0);
assertThat(number).isLessThanOrEqualTo(10);

// 컬렉션 검증
assertThat(list).hasSize(2);
assertThat(list).isEmpty();
assertThat(list).contains(item1, item2);

 

예외 검증

// 예외가 발생하는지 검증
assertThatThrownBy(() -> boardService.getBoard(999L))
    .isInstanceOf(IllegalArgumentException.class)    
    .hasMessage("Board not found with id: 999");

 

Mockito

가짜 객체(Mock)를 쉽게 만들고 관리해주는 라이브러리이며, 단위 테스트의 필수요소이다.

 

주요 어노테이션

@ExtendWith(MockitoExtension.class)

  • 클래스 레벨에 선언
  • Mockito 기능을 활성화
@ExtendWith(MockitoExtension.class)
class BoardServiceTest {
    // Mockito 사용 가능
}

 

@Mock

  • 가짜 객체를 생성
  • 실제 구현이 없는 껍데기만 있는 객체
@Mock
private BoardRepository boardRepository;  // 가짜 Repository

 

@InjectMocks

  • @Mock으로 만든 객체들을 자동으로 주입받음
  • 테스트할 실제 객체를 생성
@InjectMocks
private BoardService boardService;  // Mock Repository가 주입된 실제 Service

 

Mock 동작 정의 (Stubbing)

given().willReturn()

  • Mock 객체가 어떻게 행동해야 할지 미리 정해준다.
// "boardRepository.save()가 호출되면 board를 반환해라"
given(boardRepository.save(any(Board.class))).willReturn(board);

// "boardRepository.findById(1L)이 호출되면 Optional.of(board)를 반환해라"
given(boardRepository.findById(1L)).willReturn(Optional.of(board));

// "boardRepository.findById(999L)이 호출되면 빈 Optional을 반환해라"
given(boardRepository.findById(999L)).willReturn(Optional.empty());

 

any()매처

  • 어떤 값이든 상관없이 매칭
given(boardRepository.save(any(Board.class))).willReturn(board);
// save에 어떤 Board 객체가 들어와도 board를 반환

 

Mock 호출 검증

verify()

  • Mock 객체의 메서드가 호출되었는지 확인
// deleteById가 정확히 한 번 호출되었는지 확인
verify(boardRepository).deleteById(boardId);

// save가 한 번도 호출되지 않았는지 확인
verify(boardRepository, never()).save(any());

// findAll이 정확히 2번 호출되었는지 확인
verify(boardRepository, times(2)).findAll();

 

Spring Boot Test 어노테이션

@SpringBootTest

전체 Spring 컨텍스트를 로드하며, 통합 테스트에 사용됨

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
class BoardIntegrationTest {
    // 실제 Spring 애플리케이션이 구동됨
    // 모든 Bean이 로드됨
}

옵션 :

  • RANDOM_PORT : 랜덤 포트로 서버를 띄움
  • DEFINED_PORT : application.yml에 정의된 포트 사용
  • MOCK : 서버를 띄우지 않고 MockMvc만 사용

@DataJpaTest

JPA 관련 컴포넌트만 로드, Repository 테스트에 최적화되어 있음

@DataJpaTest
@ActiveProfiles("test")
class BoardRepositoryTest {
    // JPA Repository, EntityManager 등만 로드
    // 훨씬 빠르게 실행됨
}

특징 :

  • 자동으로 H2 인메모리 DB 사용
  • 각 테스트마다 트랜잭션 롤백
  • @Transactional이 기본 적용됨

 

@WebMvcTest

Controller 레이어만 테스트하며 Service는 Mock으로 대체한다.

@WebMvcTest(controllers = BoardController.class)
@Import(SecurityConfig.class)  // Security 설정이 필요하면 importclass BoardControllerTest {

    @Autowired
    private MockMvc mockMvc;  // HTTP 요청을 테스트할 수 있는 객체

    @MockBean  // Spring Context에 Mock Bean 등록
    private BoardService boardService;
}

@MockBean vs @Mock :

  • @MockBean : Spring Context에 Mock 객체를 등록 (Spring 테스트에서 사용)
  • @Mock : 일반 Mock 객체 생성 (Mockito 단위 테스트에서 사용)

 

@ActiveProfiles("test")

테스트용 프로필을 활성화한다. src/test/resources/application.yml을 사용

@SpringBootTest
@ActiveProfiles("test")  // H2 DB를 사용하도록 설정
class BoardIntegrationTest {
    // test 프로필 활성화
}

 

테스트코드 작성 패턴 (Given-When-Then)

테스트코드는 보통 3단계로 구성되며, 패턴을 준수할 경우 가독성이 좋아진다.

 

Given (준비)

테스트에 필요한 데이터나 상황을 세팅

// given 
BoardRequestDto requestDto = new BoardRequestDto("제목", "내용", "작성자");
Board board = Board.create("제목", "내용", "작성자");
given(boardRepository.save(any(Board.class))).willReturn(board);

 

When (실행)

실제로 테스트할 메서드를 호출

// when
BoardResponseDto responseDto = boardService.createBoard(requestDto);

 

Then (검증)

실행 결과가 기대한 값과 같은지 확인

// then
assertThat(responseDto.getTitle()).isEqualTo("제목");
assertThat(responseDto.getContent()).isEqualTo("내용");
assertThat(responseDto.getAuthor()).isEqualTo("작성자");

 

전체 예시

@Test
void createBoard() {
    // given (준비): 테스트 데이터 준비
    BoardRequestDto requestDto = new BoardRequestDto("제목", "내용", "작성자");
    Board board = Board.create("제목", "내용", "작성자");
    given(boardRepository.save(any(Board.class))).willReturn(board);
    // when (실행): 테스트할 메서드 실행
    BoardResponseDto responseDto = boardService.createBoard(requestDto);
    // then (검증): 결과 검증
    assertThat(responseDto.getTitle()).isEqualTo("제목");
    assertThat(responseDto.getContent()).isEqualTo("내용");
}

 

💡 예외를 검증하는 경우 When과 Then을 합칠 수 있음

@Test
void updateBoard_NotFound() {
    // given    
    Long boardId = 999L;    
    given(boardRepository.findById(boardId)).willReturn(Optional.empty());
    // when & then    
    assertThatThrownBy(() -> boardService.updateBoard(boardId, requestDto))
        .isInstanceOf(IllegalArgumentException.class)
        .hasMessage("Board not found with id: " + boardId);}

'JAVA > Java 실습' 카테고리의 다른 글

[Java] 테스트 코드 개념 정리하기 2  (0) 2025.12.11
[자바 알고리즘] 프로그래머스 두 개 뽑아서 더하기  (0) 2025.11.10
[JAVA Algorithm] 커머스 과제 알고리즘 기능 구현  (0) 2025.10.13
[Java Algorithm] 거스름돈 문제 풀이 예시 따라가보기  (0) 2025.09.29
[JAVA 실습] 커머스 과제 (심화)  (0) 2025.09.24
'JAVA/Java 실습' 카테고리의 다른 글
  • [Java] 테스트 코드 개념 정리하기 2
  • [자바 알고리즘] 프로그래머스 두 개 뽑아서 더하기
  • [JAVA Algorithm] 커머스 과제 알고리즘 기능 구현
  • [Java Algorithm] 거스름돈 문제 풀이 예시 따라가보기
stark77
stark77
하마의 IT 자기개발 이모저모, 백엔드 개발자로 거듭나기
  • stark77
    하마의 개발자 성장일기
    stark77
  • 전체
    오늘
    어제
    • 분류 전체보기
      • 컴퓨터구조와 운영체제
        • 컴퓨터구조
        • 운영체제
      • SQL 기초
      • Spring
        • 백엔드 기초
        • Spring 실습
      • JAVA
        • Java 실습
      • HTML&CSS
        • HTML&CSS 실습
      • Git&GitHub
        • Git&GitHub 실습
      • 내배캠 끄적끄적
        • Today I Learned
      • 유용한 툴 및 사이트 정리
      • 취미
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Github
    Spring
    객체지향
    객체지향프로그래밍
    네트워크 기초
    WebSocket
    MVC
    BEAN
    String.format
    다형성
    경합조건과 교착상태
    jsp
    웹소켓
    algorithm
    thymleaf
    SpringSecurity
    HTML&CSS
    Til
    JPA
    java
    Spriingboot
    백엔드 기초다지기
    Java 문법기초
    백엔드 기초
    RestTemplate
    git
    실시간 데이터 처리
    Stomp
    for문
    프로세스와 쓰레드
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
stark77
[Java] 테스트 코드 개념 정리하기
상단으로

티스토리툴바