
데이터 접근 기술에 대해서 더 알아보기 전에 데이터베이스에 연동하는 테스트에 대해서 알아보자.
test - application.properties 수정
src/test/resources/application.properties
spring.profiles.active=test
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=sa
logging.level.org.springframework.jdbc=debug
ItemServiceApplication
@Import(JdbcTemplateV3Config.class)
@SpringBootApplication(scanBasePackages = "hello.itemservice.web")
public class ItemServiceApplication {
ItemRepositoryTest는 @SpringBootTest를 사용한다. @SpringBootTest는 SpringBootApplication를 찾아서 설정으로 사용한다.@SpringBootApplication설정이 과거에는 MemoryConfig.class를 사용하다가 이제는 JdbcTemplateV3Config.class를 사용하도록 변경되었다. 따라서 테스트도 JdbcTemplate 을 통해 실제 데이터베이스를 호출하게 된다.테스트 실행
ItemRepositoryTest 실행

updateitem(): 성공save(): 성공findItems(): 실패 @Test
void findItems() {
//given
Item item1 = new Item("itemA-1", 10000, 10);
Item item2 = new Item("itemA-2", 20000, 20);
Item item3 = new Item("itemB-1", 30000, 30);
itemRepository.save(item1);
itemRepository.save(item2);
itemRepository.save(item3);
//둘 다 없음 검증
test(null, null, item1, item2, item3);
test("", null, item1, item2, item3);
//itemName 검증
test("itemA", null, item1, item2);
test("temA", null, item1, item2);
test("itemB", null, item3);
//maxPrice 검증
test(null, 10000, item1);
//둘 다 있음 검증
test("itemA", 10000, item1);
}
실패 원인
문제는 H2 데이터베이스에 이미 과거에 서버를 실행하면서 저장했던 데이터가 보관되어 있기 때문이다.

H2 데이터베이스를 용도에 따라 2가지로 구분하면 된다.
jdbc:h2:tcp://localhost/~/test: local에서 접근하는 서버 전용 데이터베이스jdbc:h2:tcp://localhost/~/testcase: test 케이스에서 사용하는 전용 데이터베이스데이터베이스 파일을 생성 후 테이블을 생성했다.
create table item
(
id bigint generated by default as identity,
item_name varchar(10),
price integer,
quantity integer,
primary key (id)
);
test - application.properties 변경
spring.datasource.url=jdbc:h2:tcp://localhost/~/testcase
이제 테스트를 실행
findItems() 테스트를 다시 실행하면 테스트에 실패한다.save()같은 다른 테스트가 먼저 실행되고 나서 findItems()를 실행할 때도 나타난다. 다른 테스트에서 이미 데이터를 추가했기 때문이다. 결과적으로 테스트 데이터가 오염된 것이다.테스트에서 매우 중요한 원칙
트랜잭션과 롤백 전략
@BeforeEach, @AfterEach라는 편리한 기능을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);
}
PlatformTransactionManager를 주입 받아서 사용하면 된다. 참고로 스프링 부트는 자동으로 적절한 트랜잭션 매니저를 스프링 빈으로 등록해준다.transactionManager.getTransaction(new DefaultTransactionDefinition()) 로 트랜잭션을 시작한다.@AfterEach: 각각의 테스트 케이스가 완료된 직후에 호출된다. 따라서 여기서 트랜잭션을 롤백하면 된다. 그러면 데이터를 트랜잭션 실행 전 상태로 복구할 수 있다.transactionManager.rollback(status)로 트랜잭션을 롤백한다.TestCase 데이터베이스에 데이터를 모두 삭제하고 다시 테스트를 돌려보면 성공하는 것을 확인 할 수 있다.

스프링은 테스트 데이터 초기화를 위해 트랜잭션을 적용하고 롤백하는 방식을 @Transactional 애노테이션 하나로 깔끔하게 해결해준다.
ItemRepositoryTest @Transactional 추가
@Transactional
@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);
}
반복 실행해도 계속 성공한다.

@Transactional 애노테이션은 로직이 성공적으로 수행되면 커밋하도록 동작한다.@Transactional 애노테이션을 테스트에서 사용하면 아주 특별하게 동작한다.@Transactional이 테스트에 있으면 스프링은 테스트를 트랜잭션 안에서 실행하고, 테스트가 끝나면 트랜잭션을 자동으로 롤백시켜 버린다.@Transactional이 적용된 테스트 동작 방식

@Transactional이 있으면 테스트 로직 실행 시 테스트가 끝날 때 까지 모든 로직은 트랜잭션 안에서 수행되고 테스트가 끝나면 트랜잭션을 강제로 롤백한다. 롤백에 의해 앞서 데이터베이스에 저장된 데이터는 제거된다.강제로 커밋하기 - @Commit
가끔은 데이터베이스에 데이터가 잘 보관 되었는지 최종 결과를 눈으로 확인하고 싶을 때가 있다. 이럴 때는 @Commit을 클래스 또는 메서드에 붙이면 테스트 종료 후 롤백 대신 커밋이 호출된다. @Rollback(value = false)를 사용해도 동일한 결과가 나온다.
임베디드 모드
ItemServiceApplication 변경
package hello.itemservice;
import hello.itemservice.config.JdbcTemplateV3Config;
import hello.itemservice.repository.ItemRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import javax.sql.DataSource;
@Slf4j
//@Import(MemoryConfig.class)
//@Import(JdbcTemplateV1Config.class)
//@Import(JdbcTemplateV2Config.class)
@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");
dataSource.setUrl("jdbc:h2:mem:db;DB_CLOSE_DELAY=-1");
dataSource.setUsername("sa");
dataSource.setPassword("");
return dataSource;
}
}
@Profile("test")dataSource()jdbc:h2:mem:db: 이 부분이 중요하다. 데이터소스를 만들때 이렇게만 적으면 임베디드 모드(메모리모드)로 동작하는 H2 데이터베이스를 사용할 수 있다.DB_CLOSE_DELAY=-1: 임베디드 모드에서는 데이터베이스 커넥션 연결이 모두 끊어지면 데이터베이스도 종료되는데, 그것을 방지하는 설정이다.H2 데이터베이스 서버를 끄고 ItemRepositoryTest

Table "ITEM" not found이 부분이 핵심이다. 데이터베이스 테이블이 없는 것이다.스프링은.. 다양한 기능을 제공한다
스프링 부트 - 기본 SQL 스크립트를 사용해서 데이터베이스를 초기화하는 기능
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)
);
ItemRepositoryTest 테스트

임베디드 모드 DB 관련 설정을 모두 주석처리했다.

설정을 하지 않아도 정상 수행 된다.
참고
김영한: 스프링 DB 2편 - 데이터 접근 활용 기술(인프런)
Github - https://github.com/b2b2004/Spring_DB