Spring

[Spring DB2] 데이터 접근 기술 - 테스트

gogi masidda 2024. 3. 17. 18:20

데이터 접근 기술을 테스트할 때 어떤 식으로 할 것인지를 배우는 챕터

테스트에서 중요한 원칙

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

 

테스트 - 데이터베이스 연동

먼저 데이터베이스 연동을 위해 test#application.properties에 아래 코드를 작성한다.

spring.profiles.active=test
spring.datasource.url=jdbc:h2:tcp://localhost/~/test2
spring.datasource.username=sa
spring.datasource.password=

#jdbcTemplate sql log
logging.level.org.springframework.jdbc=debug

 

@SpringBootTest
class ItemRepositoryTest {
...

이 코드에서 '@SpringBootTest'는 

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

}

이 코드의 '@SpringBootApplication을 알아서 찾아서 이 설정을 사용한다. 그래서 테스트 코드도 알아서 JdbcTemplateV3Config를 사용하게 된다.

 

하지만 이전 테스트를 그대로 돌리면 로컬에서 사용하던 데이터들이 그대로 있어서 테스트에 문제가 있다. 그래서 테스트 데이터는 격리된 환경에서 사용할 수 있어야 한다.


테스트 - 데이터베이스 분리

테스트를 다른 환경과 철저하게 분리해야 한다. 가장 간단한 방법은 테스트 전용 데이터베이스를 별도로 운영하는 것이다. 

그래서 testcase라는 데이터베이스를 따로 만들고 test#application.properties의 정보도 testcase로 바꾼다. 

 

그러면 첫 테스트는 성공적으로 이루어지지만 두번째 테스트부터는 데이터가 사라지지 않고 계속 누적되어서 실패한다. 그래서 각각의 테스트가 끝날 때마다 해당 테스트에서 추가한 데이터를 삭제해야 한다. 


테스트 - 데이터 롤백

  • 각 테스트에서 추가한 데이터를 삭제하는데 도움이 되는 것이 트랜잭션이다. 테스트가 끝나고 롤백해버리면 된다! 
  • 테스트를 하면서 데이터를 이미 저장했는데, 중간에 실패해서 롤백을 호출하지 못해도 커밋하지 않았기 때문에 데이터베이스에 데이터가 반영되지 않는다.
1. 트랜잭션 시작
2. 테스트 A 실행
3. 트랜잭션 롤백

4. 트랜잭션 시작
5. 테스트 B 실행
6. 트랜잭션 롤백

 

@SpringBootTest
class ItemRepositoryTest {

    @Autowired
    ItemRepository itemRepository;

    @Autowired
    PlatformTransactionManager transactionManager;
    TransactionStatus status; //롤백할 때 필요

    @BeforeEach
    void beforeEach() {
        //테스트 전 트랜잭션 시작
        status = transactionManager.getTransaction(new DefaultTransactionDefinition());
    }

    @AfterEach
    void afterEach() {
        //MemoryItemRepository 의 경우 제한적으로 사용
        if (itemRepository instanceof MemoryItemRepository) {
            ((MemoryItemRepository) itemRepository).clearStore();
        }
        //트랜잭션 롤백
        transactionManager.rollback(status);
    }
    ...

이렇게 BeforeEach, AfterEach를 이용해서 트랜잭션을 관리할 수 있다.

  • 트랜잭션 관리자는 PlatformTransactionManager를 주입받아서 쓰면, 스프링은 자동으로 적절한 트랜잭션 매니저를 스프링 빈에 등록해준다.
  • BeforeEach에서 'transactionManager.getTransaction(new DefaultTransactionDefinition())'으로 트랜잭션을 시작하고, AfterEach에서 'transactionManager.rollback(status)'으로 롤백한다.

이렇게 트랜잭션을 적용하고, 롤백하는 방식을 @Transactional로 깔끔하게 해결할 수 있다.

@Transtional

  • @Transtional은 로직이 성공적으로 수행되면 커밋하도록 동작한다.
  • 테스트에서 @Transtional을 사용하면 스프링은 @Test가 붙은 테스트를 트랜잭션 안에서 실행하고, 끝나면 트랜잭션을 자동으로 롤백시킨다!

그런데 어느 한 테스트의 결과를 직접 데이터베이스를 조회해서 확인하고 싶다면

@Commit
//@Rollback(value=false)
@Transactional
@Test
void save() {
...

이렇게 그 테스트 위에 @Transactional과 @Commit이나 @Rollback(value=false) 적어두면 확인할 수 있게 된다. 기본적으로 다른  테스트들은 롤백이지만 해당하는 테스트만 커밋된다.


테스트 - 임베디드 모드 DB

테스트 케이스를 실행하기 위해서 별도의 데이터베이스를 만드는 것은 번잡한 작업이다. 단순히 테스트 용으로만 사용하기 때문에 테스트가 끝나면 데이터베이스의 데이터를 모두 삭제해도 되고, 데이터베이스 자체를 삭제해도 된다. 

 

임베디드 모드 DB

  • DB를 애플리케이션에 내장해서 함께 실행한다.
  • 애플리케이션에서 자바 메모리를 함께 사용하는 라이브러리처럼 동작한다. 
@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);
	}

	@Bean
	@Profile("test")
	public DataSource dataSource() {
		log.info("메모리 데이터베이스 초기화");
		DriverManagerDataSource dataSource = new DriverManagerDataSource();
		dataSource.setDriverClassName("org.h2.Driver"); //h2 데이터베이스 드라이버 지정
		dataSource.setUrl("jdbc:h2:mem:db;DB_CLOSE_DELAY=-1");
		dataSource.setUsername("sa");
		dataSource.setPassword("");
		return dataSource;
	}
}
  • @Profile("test")를 통해 프로필이 test일 때만 스프링 빈에 등록되도록 한다.
  • jdbc:h2:mem:db데이터 소스를 만들 때 이렇게만 적어주면 임베디드 모드(메모리 모드)로 동작하는 H2 데이터베이스를 사용할 수 있다.
  • DB_CLOSE_DELAY = -1 : 임베디드 모드에서는 데이터베이스 커넥션 연결이 모두 끊어지면 데이터베이스도 종료되는데, 그것을 방지하는 설정이다.

그러면 이렇게 만들어진 임베디드 DB에 ITEM 테이블을 만들어줘야 하는데, 이것은 src/test/resource의 경로에 'schema.sql'이라는 파일을 만들어주고, 아래 코드처럼 테이블을 만드는 SQL을 작성해주면 된다.

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

그러면 이 schema.sql을 먼저 실행한 후에 테스트를 수행하게 된다. 

이렇게 하면 따로 H2 Database를 띄우지 않아도 동작한다.


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

스프링이 임베디드 DB에 대한 설정도 기본으로 제공해준다.

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

스프링 부트는 임베디드 모드로 접근하는 데이터 소스를 만들어서 제공한다.

 

이전에 작성한 test/application.properties와 itemServiceApplication에 작성한 코드를 모두 지우면 스프링 부트가 제공하는 임베디드 모드가 된다.

 

결국 이 챕터에서 배운 것을 편하게 적용하려면, test에 @Transactional을 붙이는 것만으로 끝난다.

728x90