테스트가 외부 환경(데이터베이스)에 영향을 받을 경우 테스트 자동화가 어려워진다. 이러한 문제를 해결하기 위해서 embeded database를 사용하자.
h2 데이터 베이스도 있지만 현재 UUID_TO_BIN이라는 MySql 함수를 사용하고 있어 h2 대신 embeded Mysql을 사용한다. 아래는 embeded Mysql을 사용하는 테스트 코드의 일부이다.
@Configuration
@ComponentScan(
basePackages = {"org.prgms.kdt.customer"}
)
static class Config {
@Bean
public DataSource dataSource() {
var datasource = DataSourceBuilder.create()
.url("jdbc:mysql://localhost:2215/test-order-mgmt")
.username("test")
.password("test1234!")
.type(HikariDataSource.class)
.build();
return datasource;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
// 현재 @TestInstance(TestInstance.Lifecycle.PER_CLASS)이다.
@BeforeAll
void setUp() {
var mysqlConfig = aMysqldConfig(v8_0_11)
.withCharset(Charset.UTF8)
.withPort(2215)
.withUser("test", "test1234!")
.withTimeZone("Asia/Seoul")
.build();
// embeded MySql실행
embeddedMysql = anEmbeddedMysql(mysqlConfig)
.addSchema("test-order-mgmt", classPathScript("schema.sql"))
.start();
// embeded 사용할 경우 deleteAll 필요 없다. db가 내렸가다 올라오며 데이터 모두 사라질 것이기 때문
}
@AfterAll
void cleanUp() {
// embeded MySql 종료
embeddedMysql.stop();
}
파리미터를 map을 이용하여 전달한다. 파리미터 표시는 ?
에서 :parameter_name
로 변경 되었다.
private Map<String, Object> toParaMap(Customer customer) {
return new HashMap<>(){{
put("customerId", customer.getCustomerId().toString().getBytes());
put("name", customer.getName());
put("email", customer.getEmail());
put("createdAt", Timestamp.valueOf(customer.getCreatedAt()));
put("lastLoginAt", customer.getLastLoginAt() != null ? Timestamp.valueOf(customer.getCreatedAt()) : null);
}};
}
@Override
public Customer insert(Customer customer) {
var update = jdbcTemplate.update(
"INSERT INTO customers(customer_id, name, email, created_at) values(UUID_TO_BIN(:customerId), :name, :email, :createdAt)"
, toParaMap(customer));
if(update != 1) {
throw new RuntimeException("Nothing was inserted");
}
return customer;
}
@Override
public int count() {
return jdbcTemplate.queryForObject("select count(*) from customers", Collections.emptyMap(), Integer.class);
}
@Override
public List<Customer> findAll() {
return jdbcTemplate.query("select * from customers", customerRowMapper);
}
@Override
public Optional<Customer> findById(UUID customerId) {
try {
return Optional.ofNullable(jdbcTemplate.queryForObject("select * from customers where customer_id = UUID_TO_BIN(:customerId)",
Collections.singletonMap("customerId", customerId.toString().getBytes()),
customerRowMapper));
}catch(EmptyResultDataAccessException e) {
logger.error("Got empty result", e);
return Optional.empty();
}
}
@Override
public void deleteAll() {
jdbcTemplate.update("DELETE FROM customers", Collections.emptyMap());
}
테스트 코드에서 NamedParameterJdbcTemplate을 사용하기 위해서는 아래와 같이 @Configuration을 설정해야 한다.
@Configuration
@ComponentScan(
basePackages = {"org.prgms.kdt.customer"}
)
static class Config {
@Bean
public DataSource dataSource() {
var datasource = DataSourceBuilder.create()
.url("jdbc:mysql://localhost:2215/test-order-mgmt")
.username("test")
.password("test1234!")
.type(HikariDataSource.class)
.build();
return datasource;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean
public NamedParameterJdbcTemplate namedParameterJdbcTemplate(JdbcTemplate jdbcTemplate) {
return new NamedParameterJdbcTemplate(jdbcTemplate);
}
}
만약 단순 가독성을 위해 forEach를 사용한다면 Collection의 forEach를 사용하도록 하자! 단순 가독성 향상을 위해 stream.forEach를 사용하는 경우는 stream()으로 생성된 stream 객체가 버려지는 오버헤드가 있기 때문이다.
개발자가 구현한 로직에서 발생하는 것으로서 개발자가 예외에 따른 처리 방법을 명확히 알고 적용해야한다.