Home [김영한의 스프링 DB 2편 - 데이터 접근 활용 기술] 데이터 접근 기술 - 테스트
Post
Cancel

[김영한의 스프링 DB 2편 - 데이터 접근 활용 기술] 데이터 접근 기술 - 테스트

데이터 접근 기술 - 테스트

  • 김영한님의 스프링 DB 2편 강의를 통해 테스트의 원칙, 데이터베이스 분리 전략, 트랜잭션을 활용한 롤백, 그리고 임베디드 데이터베이스 설정 방법을 정리함



테스트 환경 설정

기본 개념

  • 데이터 접근 기술을 테스트할 때는 실제 데이터베이스에 데이터를 저장하고 조회하는 것을 검증해야 함

설정 파일 구조

config_structure

  • main - application.properties

    1
    2
    3
    4
    
      spring.profiles.active=local
      spring.datasource.url=jdbc:h2:tcp://localhost/~/test
      spring.datasource.username=sa
      logging.level.org.springframework.jdbc=debug
    
  • test - application.properties

    1
    2
    3
    4
    
      spring.profiles.active=test
      spring.datasource.url=jdbc:h2:tcp://localhost/~/test
      spring.datasource.username=sa
      logging.level.org.springframework.jdbc=debug
    

발생하는 문제

  • 데이터 오염
  • 서버 실행 시 저장된 데이터가 테스트에 영향을 줌
  • 테스트 간 데이터가 공유되어 격리되지 않음

data_pollution



데이터베이스 분리 전략

해결 방법

  • 테스트 전용 데이터베이스를 별도로 운영함

    db_separation

테스트 데이터베이스 생성

  1. 데이터베이스 파일 생성

    • JDBC URL 연결
      • jdbc:h2:~/testcase (최초 한번)
      • jdbc:h2:tcp://localhost/~/testcase (이후)
  2. 테이블 생성

    1
    2
    3
    4
    5
    6
    7
    8
    
     drop table if exists item CASCADE;
     create table item (
         id bigint generated by default as identity,
         item_name varchar(10),
         price integer,
         quantity integer,
         primary key (id)
     );
    
  3. test 설정 변경

    1
    2
    3
    
     spring.profiles.active=test
     spring.datasource.url=jdbc:h2:tcp://localhost/~/testcase
     spring.datasource.username=sa
    

테스트의 원칙

  • 테스트는 다른 테스트와 격리되어야 함
  • 테스트는 반복해서 실행할 수 있어야 함



트랜잭션을 활용한 데이터 롤백

트랜잭션 롤백 전략

tx_rollback_strategy

수동 트랜잭션 관리

  • 전체 코드

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
      @SpringBootTest
      class ItemRepositoryTest {
          @Autowired ItemRepository itemRepository;
          @Autowired PlatformTransactionManager transactionManager;
            
          TransactionStatus status;
            
          @BeforeEach
          void beforeEach() {
              // 각 테스트 전에 트랜잭션 시작
              status = transactionManager.getTransaction(
                  new DefaultTransactionDefinition()
              );
          }
            
          @AfterEach
          void afterEach() {
              // 각 테스트 후에 트랜잭션 롤백
              transactionManager.rollback(status);
          }
      }
    
  • 실행 흐름

    manual_tx_flow

@Transactional 애노테이션 (권장)

  • 전체 코드

    1
    2
    3
    4
    5
    6
    7
    
      @Transactional  // 추가
      @SpringBootTest
      class ItemRepositoryTest {
          @Autowired ItemRepository itemRepository;
            
          // BeforeEach, AfterEach 불필요
      }
    
  • @Transactional 동작 원리

    transactional_flow

    구분 일반 코드 테스트 코드
    트랜잭션 시작 메서드 호출 시 테스트 시작 시
    성공 시 커밋 롤백
    예외 발생 시 롤백 롤백
    목적 데이터 영속성 보장 테스트 격리 및 데이터 정리

강제 커밋하기

  • 데이터베이스에 데이터가 실제로 저장되는지 확인하고 싶을 때 사용

  • 전체 코드

    1
    2
    3
    4
    5
    
      @Commit  // 또는 @Rollback(value = false)
      @Transactional
      @SpringBootTest
      class ItemRepositoryTest {
      }
    



임베디드 데이터베이스

임베디드 모드란?

embedded_mode

  • 별도의 DB 서버 설치나 실행이 불필요함
  • JVM 안에서 메모리 모드로 동작하며 애플리케이션 종료 시 자동으로 데이터가 삭제됨
  • 테스트 환경 구성이 매우 간단함

직접 설정하는 방법

  • 전체 코드

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
      @Bean
      @Profile("test")
      public DataSource dataSource() {
          log.info("메모리 데이터베이스 초기화");
          DriverManagerDataSource dataSource = new DriverManagerDataSource();
          dataSource.setDriverClassName("org.h2.Driver");
          dataSource.setUrl("jdbc:h2:mem:db;DB_CLOSE_DELAY=-1");
          dataSource.setUsername("sa");
          dataSource.setPassword("");
          return dataSource;
      }
    
    • jdbc:h2:mem
      • 메모리 모드로 동작
    • db
      • 데이터베이스 이름
    • DB_CLOSE_DELAY=-1
      • 임베디드 모드에서는 데이터베이스 커넥션 연결이 모두 끊어지면 데이터베이스도 종료되는데, 이를 방지하고 애플리케이션 종료 시까지 유지함
    • 기본 형식
      • jdbc:h2:mem:{database_name};{options}

테이블 자동 생성

  • src/test/resources/schema.sql 파일 생성
    • 스프링 부트는 시작 시 이 파일을 읽어 테이블을 자동으로 생성해줌

    • 전체 코드

    schema_sql_flow

데이터 초기화 스크립트 (선택사항)

  • src/test/resources/data.sql
    • schema.sql 실행 후 데이터를 초기화하고 싶을 때 사용 가능함
    1
    2
    3
    
      -- 테스트 데이터 초기화
      INSERT INTO item (item_name, price, quantity) VALUES ('testItem1', 10000, 10);
      INSERT INTO item (item_name, price, quantity) VALUES ('testItem2', 20000, 20);
    



스프링 부트 자동 설정

최종 권장 설정

  • ItemServiceApplication.java

    • DataSource 빈 정의를 제거하고 스프링 부트의 자동 설정을 활용함

    • 전체 코드

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
      @Slf4j
      @Import(JdbcTemplateV3Config.class)
      @SpringBootApplication(scanBasePackages = "hello.itemservice.web")
      public class ItemServiceApplication {
    
          public static void main(String[] args) {
              SpringApplication.run(ItemServiceApplication.class, args);
          }
    
          @Bean
          @Profile("local")
          public TestDataInit testDataInit(ItemRepository itemRepository) {
              return new TestDataInit(itemRepository);
          }
    
          // DataSource 빈 정의 제거 (스프링 부트가 자동으로 임베디드 DB 설정)
      }
    
  • test/resources/application.properties

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
      # 프로파일 설정
      spring.profiles.active=test
    
      # DataSource 설정 제거 (스프링 부트가 자동으로 임베디드 H2 설정)
      # spring.datasource.url=jdbc:h2:tcp://localhost/~/testcase
      # spring.datasource.username=sa
    
      # 로깅 설정만 유지
      logging.level.org.springframework.jdbc=debug
      logging.level.org.springframework.test.context.transaction=trace
    
  • ItemRepositoryTest.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    
      @Transactional  // 자동 롤백
      @SpringBootTest // 스프링 컨테이너 로드
      class ItemRepositoryTest {
            
          @Autowired
          ItemRepository itemRepository;
            
          @Test
          void save() {
              // given
              Item item = new Item("itemA", 10000, 10);
                
              // when
              Item savedItem = itemRepository.save(item);
                
              // then
              Item findItem = itemRepository.findById(savedItem.getId()).get();
              assertThat(findItem).isEqualTo(savedItem);
                
              // 자동 롤백 확인
          }
            
          @Test
          void updateItem() {
              // given
              Item item = new Item("itemA", 10000, 10);
              Item savedItem = itemRepository.save(item);
              Long itemId = savedItem.getId();
                
              // when
              ItemUpdateDto updateParam = new ItemUpdateDto("itemB", 20000, 30);
              itemRepository.update(itemId, updateParam);
                
              // then
              Item findItem = itemRepository.findById(itemId).get();
              assertThat(findItem.getItemName()).isEqualTo(updateParam.getItemName());
              assertThat(findItem.getPrice()).isEqualTo(updateParam.getPrice());
              assertThat(findItem.getQuantity()).isEqualTo(updateParam.getQuantity());
          }
      }
    



연습 문제

  1. 테스트 시 실제 DB 사용 중 발생하는 주요 문제는 무엇일까요?

    a. 이전 테스트 데이터 잔여

    • 테스트 시 이전 테스트나 서버 데이터가 DB에 남아있어 결과에 영향을 줌
    • 이는 테스트 간 격리가 부족하기 때문임
  2. 테스트 후 데이터 잔여 문제를 해결하기 위해 트랜잭션을 사용하는 주된 이유는 무엇일까요?

    a. 변경사항을 테스트 후 자동 롤백

    • 트랜잭션 사용은 테스트 시작 전 상태로 되돌리는(롤백) 기능 덕분임
    • 테스트가 끝난 후 변경사항을 취소해서 다음 테스트에 영향을 주지 않음
  3. Spring 테스트에서 @Transactional 어노테이션 사용의 주요 장점은 무엇일까요?

    a. 테스트 후 데이터 변경 자동 롤백

    • @Transactional을 테스트 메서드나 클래스에 붙이면, Spring이 각 테스트 후 자동으로 DB 변경을 되돌려줌
    • 수동 cleanup보다 훨씬 편리함
  4. 테스트 환경에서 H2와 같은 임베디드 DB를 사용하는 주된 이점은 무엇일까요?

    a. 외부 DB 서버 설정 불필요 및 빠른 시작

    • 임베디드 DB는 별도의 DB 서버 설치나 설정 없이 테스트 코드와 함께 실행됨
    • JVM 종료 시 데이터가 사라져 테스트 환경 관리가 쉬움
  5. Spring Boot가 테스트 환경에서 임베디드 DB 설정을 간소화하는 방법은 무엇인가요?

    a. 별도 설정 없이 기본 임베디드 DB 자동 생성

    • test 리소스의 application.properties 등에서 DB 설정을 비워두면, Spring Boot가 기본값으로 H2 임베디드 DB를 생성해서 데이터소스로 등록해줌



요약 정리

  • 좋은 테스트는 다른 테스트에 영향을 주지 않는 격리성, 언제 실행해도 같은 결과를 내는 반복성, 외부 환경에 의존하지 않는 독립성을 반드시 지켜야 함
  • 임베디드 H2 데이터베이스를 사용하면 별도의 외부 DB 설치 없이 메모리 상에서 테스트를 수행하고 종료 시 데이터가 휘발되므로 테스트 환경 구축이 매우 간편함
  • 스프링 부트는 테스트 환경(test 프로필)에서 별도 설정이 없으면 자동으로 임베디드 DB를 구성하고 schema.sql을 실행하여 테이블을 초기화해주는 편리한 기능을 제공함
  • @Transactional을 테스트 코드에 적용하면, 각 테스트 메서드 실행 후 자동으로 트랜잭션을 롤백하여 DB 데이터를 초기 상태로 복구해주므로 데이터 격리 문제를 완벽하게 해결함
  • 따라서 src/test/resources에 별도의 설정 파일과 스키마 파일을 관리하고, @Transactional과 임베디드 DB를 조합하여 안전하고 반복 가능한 테스트 환경을 구축하는 것이 권장됨



Reference

Contents

Kotlin Default Arguments와 Named Arguments

[김영한의 스프링 DB 2편 - 데이터 접근 활용 기술] 데이터 접근 기술 - MyBatis