main-application.properties
spring.profiles.active=local
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=sa
test - application.properties
spring.profiles.active=test
test에서도 db에 접근하기 위해서 url과 username을 작성한다.
spring.profiles.active=test
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=sa
Test
@SpringBootTest
class ItemRepositoryTest {
SpringBootTest가 있으면 @SpringBootApplication을 찾아서 설정으로 사용한다.
@Import(JdbcTemplateV2Config.class)
@SpringBootApplication(scanBasePackages = "hello.itemservice.web")
public class ItemServiceApplication {
설정정보로 JdbcTemplateV2Config.class를 등록해주었다.
@Configuration
@RequiredArgsConstructor
public class JdbcTemplateV2Config {
private final DataSource dataSource;
@Bean
public ItemService itemService(){
return new ItemServiceV1(itemRepository());
}
@Bean
public ItemRepository itemRepository(){
return new @Configuration
@RequiredArgsConstructor
public class JdbcTemplateV2Config {
private final DataSource dataSource;
@Bean
public ItemService itemService(){
return new ItemServiceV1(itemRepository());
}
@Bean
public ItemRepository itemRepository(){
return new JdbcTemplateItemRepositoryV2(dataSource);
}
}
(dataSource);
}
}
ItemRepository를 이전강의에서만든 JdbcTemplateItemRepositoryV2로 등록하였다.
그런데, save와 update test는 되는데 findBy~test 실행이 안된다.
이유는 나는 test코드를 짤때는 3개밖에 실제 DB에 없어서 제대로 짯는데 운영을 하면서 운영DB에 데이터가 추가되었기 때문이다.
@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);
}
현재 운영DB에 itemA-1이 여러개 있다. 고로 test통과가 되지 않는다.
그러므로 Test와 운영 DB를 분리해야한다.
testcase DB를 새로 만들고 프로퍼티 변경
spring.profiles.active=test
spring.datasource.url=jdbc:h2:tcp://localhost/~/testcase
spring.datasource.username=sa
그리고 findById를 처음 실행시 -> Test 통과
다음에 또 실행시 Test 통과 x
로그
테스트를 다시 한번 수행하면서 item이 3개가 추가되고 그래서 이전 Test의 찌꺼기가 남아있어서 제대로 수행이 안되었다.
테스트가 끝나고나서 트랜잭션을 강제로 롤백하면 데이터가 깔끔히 제거된다.(rollback하면 트랜잭션 시작 이전 상태로 복구)
만약 테스트를 하면서 데이터를 저장했는데, 중간에 테스트가 실패해서 롤백을 호출하지 못해도, 트랜잭션 커밋을 하지 않았으므로 해당 데이터가 DB에 반영되지 않는다.
테스트에 트랜잭션 추가
@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);
}
트랜잭션 관리자는 PlatformTranscationManager를 주입받아서 사용
BeforeEach: 각각의 테스트 실행전 트랜잭션을 시작한다. -> 각각의 테스트를 해당 트랜잭션 범위 안에서 실행가능
AfterEach: 테스트가 끝난후 트랜잭션 롤백으로 데이터를 트랜잭션 시작 전 상태로 복구
어? 그러면
save메서드를 보면
@Test
void save() {
//given
Item item = new Item("itemA", 10000, 10);
//when
Item savedItem = itemRepository.save(item);
//then
Item findItem = itemRepository.findById(item.getId()).get();
assertThat(findItem).isEqualTo(savedItem);
}
커밋을 하지 않았는데 save(item)으로 저장하고, findById로 어떻게 item을 볼수있는거지?
-> save하고 커밋안해도 내 트랜잭션이므로 볼 수 있다. 항상 트랜잭션 동기화 매니저에서 connection을 가져다 쓰므로, 같은 커넥션이니까 볼 수 있다.
그런데 커넥션매니저를 주입받고 rollback하고 이러한 과정들이 귀찮다.
@Transactional을 적용하면 끝난다.
@Transactional
@SpringBootTest
class ItemRepositoryTest {
...
어? 그런데 @Transactional 애노테이션은 로직이 성공적이면 커밋을 수행하도록 동작하는데 데이터가 저장되면 안되는거 아닌가? 테스트에서는 다르게 작동한다.
insert SQL로 실제 저장이 되지만, select SQL까지 Test로직이 전부 끝나면 커밋이아니라 롤백을 호출된다. 그러면 롤백에 의해 앞서 저장된 item1,2,3이 삭제된다.
만약 커밋을 하여 DB에 실제로 들어가는지 보고 싶다면 @Commit을 붙이면 된다.
정리
테스트 실행중에 데이터를 등록하고 중간에 테스타가 강제로 종료되더라도 트랜잭션 커밋을 하지 않기 때문에 데이터가 자동으로 롤백된다.
보통 데이터베이스 커넥션이 끊어지면 자동으로 롤백된다.
트랜잭션 동기화 매니저에서 커넥션을 가져다 쓰므로 같은 커넥션을 사용하므로 커밋을 하지 않아도 동일한 트랜잭션이라 데이터를 볼 수 있고 동시에 다른 테스트가 수행되어도 서로 영향을 주지 않는다.
그런데, test DB와 운영DB를 분리해서 Test하는게 복잡하다.
그냥 메모리에 올리는것 처럼 test수행시에 H2에 연결 Test끝나면 H2종료 이런식으로 메모리처럼 동작하게 할 수 는 없을까? -> 임베디드 모드
spring.profiles.active=test
#spring.datasource.url=jdbc:h2:tcp://localhost/~/testcase
#spring.datasource.username=sa
#를 통해 datasource에 대한 정보를 지워준다.
DB는 자동적으로 JVM내에 생성되므로 해당 Table에 대한 스키마만 설정해주면된다.
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)
);
경로가 중요한데 src/test/resources/schema.sql 파일이름이 맞아야한다.
그리고 그냥 실행만 시키면 된다.
기본적으로 HikariPool을 사용한느 것을 볼 수 있다.
정리
고로 Test에서 DB를 사용하기 위해서 아래 3가지만 설정하면된다.