728x90
1. 테스트 - 데이터베이스 연동
- 데이터 접근 기술은 실제 데이터베이스에 접근해서 데이터를 잘 저장하고 조회할 수 있는지 확인하는 과정이 필요
- test case는 src/test에 있기 때문에, test 실행시 src/test 내부 application.properties 파일이 우선순위를 가지고 실행
- 해당 설정 파일에도 spring.datasource.url 같은 db 연결 설정 필요
- 만약 main의 application.properties의 설정을 그대로 사용하고 싶으면 test/resources 패키지에 application.properties 파일 자체가 존재하지 않아야 사용 가능
1.1. h2 db datasource 설정 등록
- test/resources/application.properties
spring.profiles.active=test
# db 연결
spring.datasource.url=jdbc:h2:tcp://localhost/~/test # 동일한 h2 db 사용
spring.datasource.username=sa
# password 등록 안했을 시, 생략 가능
# sql문 log 확인 설정
logging.level.org.springframework.jdbc=debug
1.2. @SpringBootTest
- @SpringBootTest는 @SpringBootApplication을 찾아서 springContainer 설정으로 사용
- @SpringBootApplication 이 존재하는 class의 @Import 또한 주입 받는다.
package hello.itemservice;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
// main의 @SpringBootApplication 존재하는 class를 찾아서 동일하게 동작
class ItemServiceApplicationTests {
@Test
void contextLoads() {
}
}
2. test - database 분리
- 문제
- local에서 사용하는 appServer와 test에서 같은 db를 사용하고 있으니 test에서 문제가 발생
- 해당 문제를 해결하기 위해서 test를 다른 환경과 철저하게 분리
- 해결 방안
- 테스트 전용 db를 별도로 운영
- h2 같은 경우 인베디드memory db를 이용 (springboot default 설정 값 )
2.1. 테스트 전용 db 운영
- H2 데이터베이스 용도에 따라 2가지로 구분
- 서버용 db : jdbc:h2:tcp://localhost/~/test
- test용 db : jdbc:h2:tcp://localhost/~/testcase
- 데이터베이스 파일 생성 방법
- db 서버 완전히 종료 후 재 시작
- jdbc url : jdbc:h2:~/testcase (최초 한번) - schema 생성 과정이라 보면 된다.
- 파일 생성 확인
- ~ 경로에서 ls -arlth -> testcase.mv.db 파일 생성 확인
- jdbc url : jdbc:h2:tcp://localhost/~/testcase 접속 - schema를 생성 했으므로 local 경로로 실제 db server 접근 필요
spring.profiles.active=test
# db 연결 - test -> testcase 변경
spring.datasource.url=jdbc:h2:tcp://localhost/~/testcase
spring.datasource.username=sa
logging.level.org.springframework.jdbc=debug
- 남은 문제점
- save로 인해서 저장된 data로 반복되는 test 수행 불가
- 해결 방안
- transaction 원리를 이용
2.2. test 중요 원칙
- test는 다른 test와 격리해야 한다. - db 분리, 인베디드 db 사용
- test는 반복해서 실행할 수 있어야 한다. - clear(), @Transactional
3. test - data rollback
- spring db 1 에서 학습한 transactionManger과 status를 이용하여 status가 true, false 상관 없이 무조건 rollback하도록 @BeforeEach @AfterEach 사용
- @BeforeEach 수행 부터 @AfterEach 오기전까지 동일 connection을 사용하는 transaction 수행됨
package hello.itemservice.domain;
@SpringBootTest
class ItemRepositoryTest {
// DataSource와 txManger는 springboot에서 자동으로 생성해놓았다.
@Autowired
ItemRepository itemRepository;
//tx 관련 code
@Autowired
PlatformTransactionManager transactionManager; // spring boot에서 생성해줌
TransactionStatus status; // status를 통해서 commit, rollback 수행
@BeforeEach
void beforeEach(){
// tx 시작
status = transactionManager.getTransaction(new DefaultTransactionDefinition());
}
@AfterEach
void afterEach() {
//MemoryItemRepository 의 경우 제한적으로 사용
if (itemRepository instanceof MemoryItemRepository) {
((MemoryItemRepository) itemRepository).clearStore();
}
// tx rollback
transactionManager.rollback(status);
}
...
@Test
}
4. test - @Transactional
- test에서 사용시, 조금 특별하게 사용
- test 내부에서 @Transactional이 존재시, spring은 transaction 내부에 test를 실행하고, test 종료시 로직의 성공적인 수행여부와 관계 없이 transaction을 항상 rollback 시킴
- test에 사용되는 @Transactional도 Class, method 모두 원하는 부분에 적용 가능
- 참고 - @Service test 시, @Service 내부에 @Transactional도 존재할 텐데 어떻게 충돌을 막을까?
- test case의 @Transactional 과 @Service, @Repository에 있는 @Transactional 도 Test에서 시작한 트랜잭션에 참여
= Test가 종료될 때까지 Test의 connection을 사용한다는 의미
- test case의 @Transactional 과 @Service, @Repository에 있는 @Transactional 도 Test에서 시작한 트랜잭션에 참여
- test의 @Transactional 강제 commit 하기
- @Commit
- @Rollback(value = false)
package hello.itemservice.domain;
// @Transactional, @Commit,@Rollback(value = false) 모두 class, method 영역에 설정 가능
@Transactional
@Commit == @Rollback(value = false)
@SpringBootTest
class ItemRepositoryTest {
// DataSource와 txManger는 springboot에서 자동으로 생성해놓았다.
@Autowired
ItemRepository itemRepository;
//tx 관련 code
/*
@Autowired
PlatformTransactionManager transactionManager;
TransactionStatus status;
@BeforeEach
void beforeEach(){
// tx 시작
status = transactionManager.getTransaction(new DefaultTransactionDefinition());
}
*/
@AfterEach
void afterEach() {
// MemoryItemRepository 의 경우 제한적으로 사용
if (itemRepository instanceof MemoryItemRepository) {
((MemoryItemRepository) itemRepository).clearStore();
}
// tx rollback
// transactionManager.rollback(status);
}
// @Rollback(value = false) == @Commit
@Test
5. test - 임베디드 모드 db
- 사용 이유
- test case를 실행하려고 별도의 db를 설치하고, 운영하는 것이 비효율적
- 단순 검증 용도로 사용하므로 test 종료시 db의 data를 모두 삭제해도 된다.
- 더해서 test 종료시, db 자체를 날려도 된다.
- 임베디드 모드
- H2 db는 java로 개발 되어있고 JVM 안에서 memory 모드로 동작한느 특별한 기능 제공
- app 실행할 때 H2 데이터베이스도 해당 JVM 메모리에 포함해서 함께 실행 하 수 있다.
- db를 app에 내장해서 함께 실행한다고 Embedded mode라고 한다.
- app 종료시, embedded mode로 동장하는 H2 db도 함께 종료되고 data도 모두 사라진다.
= java libarary처럼 동작한다.
- 수동, 자동 공통 적용 부분
- db database 관련 설정 등록 하면 안된다.
- main/resources/schema.sql 파일에 table 생성 sql 등록이 필요하다.
5.1. 메모리 db용 sql 파일 생성 (공통)
- embedded 용 db기 때문에 매번 사라졌다 생기므로 table 자체가 저장 될 수 없다.
- repository에 ddl 로 table을 넣을 수도 있지만 번거롭다.
- 조금 더 편리한 emebedded(=memory) db 용 sql 파일을 생성
- 경로 : main/resources/schema.sql
-- memory h2는 현재 table이 생성되어있지 않는 상태
-- 해당 file에서 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)
);
5.2. application.properties database 설정 (공통)
- 우선 순위 : @SpringBootApplication 내부 @Bean 수동 등록 > @application.properties 수동등록 > 자동등록
- 수동 모드일 경우는 결국 최상단의 @SpringBootApplication의 main method 내부에서 @Bean을 등록하기 때문에 application.properties의 database 등록이 덮어지지만
- 자동 모드일 경우, embedded 모드가 수행되지 않게 된다. application.properties에서 database를 수동으로 등록했기 때문
- 결론 : 헷갈리지 않게 embedded 모드 쓸꺼면 application.properties에서 database 등록 하지 말 것
spring.profiles.active=test
# db 연결 - test -> testcase 변경
# spring.datasource.url=jdbc:h2:tcp://localhost/~/testcase
# spring.datasource.username=sa
logging.level.org.springframework.jdbc=debug
5.3. 임베디드 모드 직접 사용
5.3.1. application Class
package hello.itemservice;
@Slf4j
@Import(JdbcTemplateV3Config.class)
@SpringBootApplication(scanBasePackages = "hello.itemservice.web")
public class ItemServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ItemServiceApplication.class, args);
}
// application.properties의 설정 중 local 환경일 경우 @Bean으로 등록한다.
@Bean
@Profile("local")
public TestDataInit testDataInit(ItemRepository itemRepository) {
return new TestDataInit(itemRepository);
}
@Bean
@Profile("test")
public DataSource dataSource() {
log.info("memory database init");
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;
}
}
- embedded용 datasource 및 Driver @Bean 등록 - 핵심
@Bean
@Profile("test")
public DataSource dataSource() {
log.info("memory database init");
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") : 프로필이 test일 경우에만 해당 class를 스프링 빈으로 등록
- jdbc:h2:mem:db : 데이터소스를 만들때 이렇게만 적으면 임베디드 모드로 동작하는 h2 사용가능
- DB_CLOSE_DELAY=-1 : 임베디드 모드에서 db connection 연결이 모두 끊어지면 db도 종료되는데, 그것을 방지하는 설정
- datasource와 driver만 잘 설정하면 된다.
5.4. test - springboot와 임베디드 모드
- spring boot는 @Bean 등록과정을 자동으로 처리
- @Bean 제거
- 수동 모드와 동일하게 작동
package hello.itemservice;
@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);
}
}
6. Test / main Profile 실험
- application.properties == AP라고 표현
- test 패키지 경로에 AP file 자체가 없으면 main의 AP를 properties로 사용
- main AP에 datasource 등록이 되어있으면 해당 db를 이용해서 test 수행
- main AP에 datasource 등록 없으면 embedded mode db 수행
- test 패키지 경로에 AP file 이 있는 경우
- 내부에 profile 설정 여부와 관계없이 해당 test AP를 이용해서 springboot test 수행- test AP에 datasource 등록이 되어있으면 해당 db를 이용해서 test 수행
- test AP에 datasource 등록 없으면 embedded mode db 수행
- test class에 @Transactional 존재 여부시, 달라지는 log
- mem이 나오는 것이 확실이 embedded mode임을 나타내는데 2번의 경우도 db를 실행안해도 test가 정상 동작하므로 embedded 모드라고 추정할 수 있다.
- @Transactional 존재 시,
> [HikariProxyConnection@1631143060 wrapping conn0: url=jdbc:h2:mem:b175c7c8-3ee4-4c58-9773-f0be5b88066d user=SA] - @Transactional 존재 안할 시,
> com.h2database/h2/1.4.200/f7533fe7cb8e99c87a43d325a77b4b678ad9031a/h2-1.4.200
- @Transactional 존재 시,