개발환경
IDE : intelliJ
FrameWork : springboot 3.4
Launguage : java 17
BuildTool : gradle
TestTool : Junit5
데이터베이스 단위 테스트시 발생하는 임피던스 불일치 (Impedance Mismatch)
단위 테스트는 코드를 격리시켜 실행해야하고, 작성과 구동이 쉬워야하며, 실행속도가 빨라야한다. 그렇기 때문에 관계형 모델을 기반으로 작성된 데이터베이스와 애플리케이션을 연결할 때 차이가 발생하는데, 이를 임피던스 불일치라고 표현한다. 이러한 임피던스 불일치를 해소하기 위해 ORM, IBATIS를 사용한다. 이번 글에서는 ORM 을 사용할 것이다.
더보기
더보기
📝 ORM
관계형 데이터베이스의 데이터를 객체 지향 프로그래밍의 객체로 변환하거나 반대로 변환하는 기법
📝 Hibernate
자바 진영의 ORM 프레임워크. 하버네이트는 객체 지향 도메인 모델을 관계형 데이터베이스 테이블에 매핑할 때 사용한다. 하버네이트는 객체 지향 도메인 모델과 관계형 데이터베이스 모델간에 서로 호환되지 않는 문제를 해결하기 위해 데이터베이스에 직접 접근하지 않고 객체를 조작함으로써 데이터를 변경한다
예제 소개
📕 요구사항
1. 국가 정보가 저장된 데이터베이스에서 국가 데이터를 기준에 따라 조회 하고, 조회된 데이터가 예상치와 일치하는지 비교한다.
ㄴ 기준1 : 전체 국가를 조회
ㄴ 기준2 : 특정한 이름으로 시작하는 국가 데이터를 조회
📌 스프링 JDBC 애플리케이션 테스트
- 테스트를 위한 기타 서비스 코드
더보기
더보기
// JdbcDaoSupport :: 데이터베이스 파라미터 구성과 전송을 쉽게 만들어주는 스프링 JDBC 클래스
public class CountryDao extends JdbcDaoSupport {
private static final String GET_ALL_COUNTRIES_SQL = "select * from country";
private static final String GET_COUNTRIES_BY_NAME_SQL = "select * from country where name like :name";
private static final CountryRowMapper COUNTRY_ROW_MAPPER = new CountryRowMapper();
/**
* 조건없이 모든 국가데이터 조회
*/
public List<Country> getCountryList() {
List<Country> countryList = getJdbcTemplate().query(GET_ALL_COUNTRIES_SQL, COUNTRY_ROW_MAPPER);
return countryList;
}
/**
* 특정 이름으로 시작하는 국가 데이터만 조회
*/
public List<Country> getCountryListStartWith(String name) {
NamedParameterJdbcTemplate namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(getDataSource());
SqlParameterSource sqlParameterSource = new MapSqlParameterSource("name", name + "%");
return namedParameterJdbcTemplate.query(GET_COUNTRIES_BY_NAME_SQL, sqlParameterSource, COUNTRY_ROW_MAPPER);
}
}
// 초기 데이터 셋팅
public class CountriesLoader extends JdbcDaoSupport {
private static final String LOAD_COUNTRIES_SQL = "insert into country (name, code_name) values ";
public static final String[][] COUNTRY_INIT_DATA = {{"Australia", "AU"}, {"Canada", "CA"}, {"France", "FR"},
{"Germany", "DE"}, {"Italy", "IT"}, {"Japan", "JP"}, {"Romania", "RO"},
{"Russian Federation", "RU"}, {"Spain", "ES"}, {"Switzerland", "CH"},
{"United Kingdom", "UK"}, {"United States", "US"}};
public void loadCountries() {
for (String[] countryData : COUNTRY_INIT_DATA) {
String sql = LOAD_COUNTRIES_SQL + "('" + countryData[0] + "', '" + countryData[1] + "');";
getJdbcTemplate().execute(sql);
}
}
}
// RowMapper :: 데이터베이스에서 가져온 ResultSet를 특정 객체에 매핑해서 반환해주는 스프링 JDBC
public class CountryRowMapper implements RowMapper<Country> {
public static final String NAME = "name";
public static final String CODE_NAME = "code_name";
@Override
public Country mapRow(ResultSet resultSet, int i) throws SQLException {
Country country = new Country(resultSet.getString(NAME), resultSet.getString(CODE_NAME));
return country;
}
}
- 테스트 코드
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:application-context.xml") // countryDao, countriesLoader 가 설정 되어 있음
public class CountriesDatabaseTest {
@Autowired
private CountryDao countryDao;
@Autowired
private CountriesLoader countriesLoader;
private List<Country> expectedCountryList = new ArrayList<Country>();
private List<Country> expectedCountryListStartsWithA = new ArrayList<Country>();
@BeforeEach
public void setUp() {
initExpectedCountryLists();
countriesLoader.loadCountries();
}
@Test
@DirtiesContext // 테스트가 콘텍스트를 변경했을 때 사용. 테스트마다 새로운 콘텍스트가 제공되므로 데이터베이스의 부담이 줄어든다
public void testCountryList() {
List<Country> countryList = countryDao.getCountryList();
assertNotNull(countryList);
assertEquals(expectedCountryList.size(), countryList.size());
for (int i = 0; i < expectedCountryList.size(); i++) {
assertEquals(expectedCountryList.get(i), countryList.get(i));
}
}
@Test
@DirtiesContext
public void testCountryListStartsWithA() {
List<Country> countryList = countryDao.getCountryListStartWith("A");
assertNotNull(countryList);
assertEquals(expectedCountryListStartsWithA.size(), countryList.size());
for (int i = 0; i < expectedCountryListStartsWithA.size(); i++) {
assertEquals(expectedCountryListStartsWithA.get(i), countryList.get(i));
}
}
private void initExpectedCountryLists() {
for (int i = 0; i < CountriesLoader.COUNTRY_INIT_DATA.length; i++) {
String[] countryInitData = CountriesLoader.COUNTRY_INIT_DATA[i];
Country country = new Country(countryInitData[0], countryInitData[1]);
expectedCountryList.add(country);
if (country.getName().startsWith("A")) {
expectedCountryListStartsWithA.add(country);
}
}
}
}
📌 Hibernate 애플리케이션 테스트
- 테스트 코드
public class CountriesHibernateTest {
private EntityManagerFactory emf;
private EntityManager em;
private List<Country> expectedCountryList = new ArrayList<>();
private List<Country> expectedCountryListStartsWithA = new ArrayList<>();
public static final String[][] COUNTRY_INIT_DATA = {{"Australia", "AU"}, {"Canada", "CA"}, {"France", "FR"},
{"Germany", "DE"}, {"Italy", "IT"}, {"Japan", "JP"}, {"Romania", "RO"},
{"Russian Federation", "RU"}, {"Spain", "ES"}, {"Switzerland", "CH"},
{"United Kingdom", "UK"}, {"United States", "US"}};
@BeforeEach
public void setUp() {
initExpectedCountryLists();
emf = Persistence.createEntityManagerFactory("manning.hibernate");
em = emf.createEntityManager();
// 여기서부터 생성된 국가 객체를 인터페이스에 영속시킴
em.getTransaction().begin();
for (int i = 0; i < COUNTRY_INIT_DATA.length; i++) {
String[] countryInitData = COUNTRY_INIT_DATA[i];
Country country = new Country(countryInitData[0], countryInitData[1]);
em.persist(country);
}
em.getTransaction().commit();
// -- 여기서부터 생성된 국가 객체를 인터페이스에 영속시킴
}
@Test
public void testCountryList() {
List<Country> countryList = em.createQuery("select c from Country c").getResultList();
assertNotNull(countryList);
assertEquals(COUNTRY_INIT_DATA.length, countryList.size());
for (int i = 0; i < expectedCountryList.size(); i++) {
assertEquals(expectedCountryList.get(i), countryList.get(i));
}
}
@Test
public void testCountryListStartsWithA() {
List<Country> countryList = em.createQuery("select c from Country c where c.name like 'A%'").getResultList();
assertNotNull(countryList);
assertEquals(expectedCountryListStartsWithA.size(), countryList.size());
for (int i = 0; i < expectedCountryListStartsWithA.size(); i++) {
assertEquals(expectedCountryListStartsWithA.get(i), countryList.get(i));
}
}
@AfterEach
public void dropDown() {
em.close();
emf.close();
}
private void initExpectedCountryLists() {
for (int i = 0; i < COUNTRY_INIT_DATA.length; i++) {
String[] countryInitData = COUNTRY_INIT_DATA[i];
Country country = new Country(countryInitData[0], countryInitData[1]);
expectedCountryList.add(country);
if (country.getName().startsWith("A")) {
expectedCountryListStartsWithA.add(country);
}
}
}
}
📌 스프링 Hibernate 애플리케이션 테스트
- 테스트를 위한 기타 서비스 코드
더보기
더보기
public class CountryService {
@PersistenceContext
private EntityManager em;
public static final String[][] COUNTRY_INIT_DATA = {{"Australia", "AU"}, {"Canada", "CA"}, {"France", "FR"},
{"Germany", "DE"}, {"Italy", "IT"}, {"Japan", "JP"}, {"Romania", "RO"},
{"Russian Federation", "RU"}, {"Spain", "ES"}, {"Switzerland", "CH"},
{"United Kingdom", "UK"}, {"United States", "US"}};
@Transactional
public void init() {
for (int i = 0; i < COUNTRY_INIT_DATA.length; i++) {
String[] countryInitData = COUNTRY_INIT_DATA[i];
Country country = new Country(countryInitData[0], countryInitData[1]);
em.persist(country);
}
}
@Transactional
public void clear() {
em.createQuery("delete from Country c").executeUpdate();
}
public List<Country> getAllCountries() {
return em.createQuery("select c from Country c").getResultList();
}
public List<Country> getCountriesStartingWithA() {
return em.createQuery("select c from Country c where c.name like 'A%'").getResultList();
}
}
- 테스트 코드
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:application-context.xml")
public class CountriesHibernateTest {
@Autowired
private CountryService countryService;
private List<Country> expectedCountryList = new ArrayList<>();
private List<Country> expectedCountryListStartsWithA = new ArrayList<>();
@BeforeEach
public void setUp() {
countryService.init();
initExpectedCountryLists();
}
@Test
public void testCountryList() {
List<Country> countryList = countryService.getAllCountries();
assertNotNull(countryList);
assertEquals(COUNTRY_INIT_DATA.length, countryList.size());
for (int i = 0; i < expectedCountryList.size(); i++) {
assertEquals(expectedCountryList.get(i), countryList.get(i));
}
}
@Test
public void testCountryListStartsWithA() {
List<Country> countryList = countryService.getCountriesStartingWithA();
assertNotNull(countryList);
assertEquals(expectedCountryListStartsWithA.size(), countryList.size());
for (int i = 0; i < expectedCountryListStartsWithA.size(); i++) {
assertEquals(expectedCountryListStartsWithA.get(i), countryList.get(i));
}
}
@AfterEach
public void dropDown() {
countryService.clear();
}
private void initExpectedCountryLists() {
for (int i = 0; i < COUNTRY_INIT_DATA.length; i++) {
String[] countryInitData = COUNTRY_INIT_DATA[i];
Country country = new Country(countryInitData[0], countryInitData[1]);
expectedCountryList.add(country);
if (country.getName().startsWith("A")) {
expectedCountryListStartsWithA.add(country);
}
}
}
}