Java Category/Spring

[Spring DB] 데이터 접근 계층 테스트

ReBugs 2024. 4. 2.

이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다.


@SpringBootTest와 @SpringBootApplication

@SpringBootApplication

@SpringBootApplication은 Spring Boot 애플리케이션의 주 진입점에 위치하는 어노테이션이다. 이 어노테이션은 @Configuration, @EnableAutoConfiguration, @ComponentScan 어노테이션들의 기능을 합친 것으로, Spring Boot 애플리케이션을 자동 설정하고, 애플리케이션 컨텍스트에서 빈을 검색하며, 추가적인 설정을 로드하는 역할을 한다.

 

기본적으로, 이 어노테이션이 붙은 클래스는 애플리케이션의 메인 클래스로, 애플리케이션 실행 시 스프링 부트의 자동 설정 메커니즘을 활성화한다.

 

@SpringBootTest

@SpringBootTest는 테스트 클래스에 사용되며, Spring Boot 애플리케이션 컨텍스트를 테스트 환경에서 전체적으로 로드한다. 이를 통해 애플리케이션의 통합 테스트를 수행할 수 있게 되며, 실제 실행 환경과 유사한 조건에서 애플리케이션의 다양한 컴포넌트들을 테스트할 수 있다.

 

@SpringBootTest는 @SpringBootApplication과 연결되어 있다고 볼 수 있는데, 이는 @SpringBootTest가 애플리케이션 컨텍스트를 로드할 때 @SpringBootApplication 어노테이션이 붙은 메인 클래스를 기반으로 애플리케이션의 설정과 구성을 로드하기 때문이다.

 

관계

@SpringBootTest를 사용하는 테스트는 @SpringBootApplication 어노테이션이 붙은 애플리케이션 메인 클래스의 설정을 기반으로 실행된다. 이는 테스트 시 실제 애플리케이션을 실행할 때와 동일한 스프링 부트의 자동 설정, 컴포넌트 스캔, 외부 설정 로드 등이 적용됨을 의미한다.

따라서, @SpringBootTest를 사용한 테스트는 애플리케이션의 구성 요소들이 실제 환경에서 어떻게 상호작용하는지를 포괄적으로 검증할 수 있는 환경을 제공받는다.

 

요약하자면, @SpringBootTest와 @SpringBootApplication은 Spring Boot 애플리케이션에서 각각 테스트와 애플리케이션 구동을 위해 사용되며, @SpringBootTest로 작성된 테스트는 @SpringBootApplication이 적용된 애플리케이션의 전체 컨텍스트와 설정을 기반으로 실행된다. 이 관계 덕분에 개발자는 애플리케이션의 통합 테스트를 실제와 유사한 환경에서 수행할 수 있게 된다.

 

데이터 롤백

테스트에서 매우 중요한 원칙은 다음과 같다.

-테스트는 다른 테스트와 격리해야 한다.

-테스트는 반복해서 실행할 수 있어야 한다.

 

이때 도움이 되는 것이 바로 트랜잭션이다.
테스트가 끝나고 나서 트랜잭션을 강제로 롤백해버리면 데이터가 깔끔하게 제거된다.
테스트를 하면서 데이터를 이미 저장했는데, 중간에 테스트가 실패해서 롤백을 호출하지 못해도 괜찮다. 트랜잭션을 커밋하지 않았기 때문에 데이터베이스에 해당 데이터가 반영되지 않는다.
이렇게 트랜잭션을 활용하면 테스트가 끝나고 나서 데이터를 깔끔하게 원래 상태로 되돌릴 수 있다.

//트랜잭션 관련 코드
@Autowired
PlatformTransactionManager transactionManager;
TransactionStatus status;
 @BeforeEach
 void beforeEach() {
    //트랜잭션 시작
    status = transactionManager.getTransaction(new DefaultTransactionDefinition());
}

 @AfterEach
 void afterEach() {
    //트랜잭션 롤백
    transactionManager.rollback(status);
}

PlatformTransactionManager와 TransactionStatus는 Spring Framework에서 트랜잭션 관리를 위해 사용되는 주요 인터페이스이다. 

 

-PlatformTransactionManager는 트랜잭션 관리의 핵심 인터페이스로, 트랜잭션을 시작, 커밋, 롤백하는 데 필요한 메소드를 제공한다.

-TransactionStatus는 진행 중인 트랜잭션의 상태를 나타내며, 트랜잭션이 새롭게 시작되었는지, 롤백이 필요한지 등의 정보를 포함한다.

여기서 transactionManager 객체는 스프링의 PlatformTransactionManager 인터페이스의 구현체를 참조한다. 

 

status 객체는 transactionManager.getTransaction() 메소드를 호출할 때 반환되는 TransactionStatus 객체이다. 이 두 객체를 사용하여 애플리케이션 내에서 트랜잭션을 명시적으로 제어할 수 있다.

 

status = transactionManager.getTransaction(new DefaultTransactionDefinition()); 코드는 트랜잭션 관리 기능을 사용하여 새로운 트랜잭션을 시작하는 과정을 나타낸다. 이 코드는 PlatformTransactionManager 인터페이스의 getTransaction() 메소드를 호출하여 트랜잭션을 시작하고, 트랜잭션의 상태를 나타내는 TransactionStatus 객체를 반환받는다.

 

new DefaultTransactionDefinition()은 기본 트랜잭션 속성과 설정으로 트랜잭션을 시작하기 위한 파라미터다.

transactionManager.rollback(status); 코드는 시작된 트랜잭션이 성공적으로 완료되지 못했을 때, 즉 예외가 발생했거나 비즈니스 로직이 실패하여 트랜잭션을 롤백해야 할 상황에서 사용된다. 여기서 rollback() 메소드는 주어진 TransactionStatus 객체에 해당하는 트랜잭션을 롤백한다.

 

테스트 코드에서 @Transactional

테스트 코드에서 @Transactional 어노테이션을 사용하는 것은 매우 흔한 패턴이다. 이 어노테이션은 테스트 메소드나 테스트 클래스에 적용할 수 있으며, 테스트가 실행될 때 트랜잭션을 시작하고 테스트가 완료된 후에는 롤백을 수행한다. 

이러한 접근 방식은 테스트 중에 데이터베이스에 변경을 가하는 작업을 수행할 때 매우 유용하다. 테스트가 종료되면, 테스트 중에 수행된 모든 데이터 변경 사항이 롤백되어 데이터베이스의 상태가 테스트 실행 전과 동일하게 유지된다.

@Transactional의 장점

  • 격리성: 각 테스트 메소드가 서로에게 영향을 주지 않고 독립적으로 실행될 수 있게 해준다. 이는 테스트 간 데이터 충돌을 방지하고 예측 가능한 테스트 결과를 보장한다.
  • 일관성: 테스트 실행 전후로 데이터베이스의 상태가 변경되지 않으므로, 테스트 환경의 일관성을 유지할 수 있다.
  • 편의성: 데이터베이스를 직접 청소하지 않고도 테스트 후 데이터베이스를 깨끗한 상태로 유지할 수 있다.

 

@RunWith(SpringRunner.class)
@SpringBootTest
public class SomeServiceTest {

    @Autowired
    private SomeService someService;

    @Test
    @Transactional
    public void testSomeServiceMethod() {
        // 테스트 수행
        someService.performSomeDataModification();

        // 검증 로직
        // ...

        // 테스트가 종료되면, performSomeDataModification에 의한 데이터 변경 사항이 롤백된다.
    }
}

이 예제에서, @Transactional 어노테이션은 testSomeServiceMethod 테스트 메소드에 적용되어 있다. 이 메소드에서는 SomeService의 performSomeDataModification 메소드를 호출하여 데이터베이스에 변화를 주는 작업을 수행한다. 

테스트 메소드 실행이 완료되면, 실행 중에 발생한 모든 데이터 변경은 롤백되어 데이터베이스의 상태가 테스트 실행 전과 동일하게 유지된다.

주의 사항

  • @Transactional을 테스트에 사용할 때는, 롤백으로 인해 테스트가 데이터베이스에 영구적인 변경을 가하지 않음을 이해하는 것이 중요하다.
    테스트에서 @Transactional의 롤백 동작을 원치 않는 경우 @Commit 어노테이션을 사용하여 트랜잭션의 커밋을 강제할 수 있다. 그러나 이는 매우 신중하게 사용되어야 한다.

@Transactional 어노테이션은 테스트에서 데이터베이스 상태를 관리하는 강력한 도구이지만, 그 사용 방법과 영향을 잘 이해하고 사용해야 한다.

 

임베디드 모드 DB

임베디드 모드 데이터베이스(Embedded Database)는 애플리케이션과 함께 실행되며, 별도의 서버 설치나 관리가 필요 없는 데이터베이스를 말한다. 

 

이러한 데이터베이스는 주로 개발이나 테스트 환경에서 사용되며, 실제 운영 환경에서는 사용되는 경우가 적다. 임베디드 데이터베이스의 가장 큰 장점은 설치가 간편하고, 애플리케이션과 동일한 라이프사이클을 가진다는 것이다.

임베디드 모드 데이터베이스의 특징

  • 경량성: 별도의 서버 설치나 관리 없이 애플리케이션 내부에서 실행되므로, 경량화가 잘 되어 있고 설정이 간단하다.
  • 이동성: 애플리케이션과 함께 데이터베이스도 패키지화되어 배포될 수 있으므로, 어디서든 같은 환경을 빠르게 구축할 수 있다.
  • 개발 및 테스트 용이성: 개발 및 테스트 단계에서 실제 데이터베이스 서버를 구축하지 않고도, 실제 데이터베이스와 유사한 환경을 손쉽게 구성할 수 있다.
  • 독립성: 애플리케이션과 동일한 프로세스에서 실행되므로, 애플리케이션 실행이 종료되면 데이터베이스도 함께 종료된다.

 

H2 Database

Java로 작성된 경량화된 관계형 데이터베이스로, 매우 빠른 데이터베이스 엔진을 제공한다. 개발 및 테스트 환경에서 널리 사용된다.

 

스프링 부트에서 H2 데이터 베이스를 사용하려면 아래와 같이 build.gradle에 의존성을 주입해야 한다.

dependencies {
	...
	//H2 데이터베이스 추가
	runtimeOnly 'com.h2database:h2'
    ...
}

 

H2 데이터베이스는 자바로 개발되어 있고, JVM안에서 메모리 모드로 동작하는 특별한 기능을 제공한다. 

그래서 애플 리케이션을 실행할 때 H2 데이터베이스도 해당 JVM 메모리에 포함해서 함께 실행할 수 있다. DB를 애플리케이션에 내장해서 함께 실행한다고 해서 임베디드 모드(Embedded mode)라 한다. 물론 애플리케이션이 종료되면 임베디드 모드로 동작하는 H2 데이터베이스도 함께 종료되고, 데이터도 모두 사라진다. 

 

쉽게 이야기해서 애플리케이션에서 자바 메모리를 함께 사용하는 라이브러리처럼 동작하는 것이다.

 

@Bean
@Profile("test")
public DataSource dataSource() {
    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;
}
  • @Bean: 스프링 컨테이너에 의해 관리되는 빈을 정의한다. 이 메소드가 반환하는 객체(DataSource)는 스프링 애플리케이션 컨텍스트에 등록되어 다른 빈들에서 주입하여 사용할 수 있다.
  • @Profile("test"): 이 어노테이션이 붙은 빈은 "test" 프로파일이 활성화되었을 때만 생성된다. 프로파일을 이용해 개발, 테스트, 운영 환경 등 여러 환경에서 다른 설정을 적용할 수 있다.
  • DriverManagerDataSource: 스프링이 제공하는 간단한 DataSource 구현체로, 각종 데이터베이스 연결 정보(드라이버 클래스명, URL, 사용자 이름, 비밀번호)를 설정할 수 있다.
  • dataSource.setDriverClassName("org.h2.Driver"): JDBC 드라이버 클래스를 지정한다. H2 데이터베이스를 사용하기 때문에 H2 드라이버 클래스명을 설정한다.
  • dataSource.setUrl("jdbc:h2:mem:db;DB_CLOSE_DELAY=-1"): 데이터베이스 연결 URL을 설정한다. 여기서 jdbc:h2:mem:db는 메모리 내에서 실행되는 H2 데이터베이스에 연결하라는 의미이고, DB_CLOSE_DELAY=-1 옵션은 애플리케이션이 실행되는 동안 데이터베이스가 유지되도록 한다.
  • dataSource.setUsername("sa")와 dataSource.setPassword(""): 데이터베이스 연결을 위한 사용자 이름과 비밀번호를 설정한다. H2의 기본 사용자 이름은 "sa"이고, 비밀번호는 공백이다.

 

애플리케이션에서 자바 메모리를 함께 사용하기 때문에 프로젝트 안에 해당 데이터베이스가 있어야 한다.

예를 들면 아래와 같다.

src/test/resources/schema.sql

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)
 );

 

SQL 스크립트를 사용해서 데이터베이스를 초기화하는 자세한 방법은 다음 스프링 부트 공식 메뉴얼을 참고

https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.data- initialization.using-basic-sql-scripts

 

 

스프링 부트와 임베디드 모드

application.properties

spring.profiles.active=test

#spring.datasource.url=jdbc:h2:tcp://localhost/~/testcase
#spring.datasource.username=sa

spring.datasource.url , spring.datasource.username 를 사용하지 않도록 # 을 사용해서 주석처리 했다.
이렇게 하면 데이터베이스에 접근하는 모든 설정 정보가 사라지게 된다.
이렇게 별다른 정보가 없으면 스프링 부트는 임베디드 모드로 접근하는 데이터소스( DataSource )를 만들어서 제공한다.

 

즉, 스프링 부트는 데이터베이스에 대한 별다른 설정이 없으면 임베디드 데이터베이스를 사용한다.

 

 

'Java Category > Spring' 카테고리의 다른 글

[Spring DB] 스프링 트랜잭션의 이해  (0) 2024.04.06
[Spring DB] MyBatis  (0) 2024.04.03
[Spring DB] SimpleJdbcInsert  (0) 2024.04.01
[Spring DB] NamedParameterJdbcTemplate  (0) 2024.03.31
[Spring DB] JDBC Template  (0) 2024.03.29

댓글