Dev/TDD

Junit In Action - TDD를 위한 테스트 원칙, 도구 및 활용 Review - 데이터베이스 애플리케이션 테스트

린네의 2025. 2. 4. 22:28

개발환경

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);
            }
        }
    }
}