Whiteship's Note


[회사일] DAO 테스트 만들기

프로젝트/SLT : 2010.06.01 18:55


이전 코드에서는 뭐 테스트 해보고 싶을 만한게 없었다. 사실 하두 자주 써먹었고 거의 API를 그대로 쓴 코드라서 테스트 하지 않아도 확신이 서는 코드가 대부분이었다. 그 중에서도 굳이 가장 불안한 코드를 꼽으라면 DAO 코드가 되겠다. 나머진 그냥 단순 위임이라 뭐 의심할께 없다.

DAO 코드를 보자.

    public List<Code> list() {
        return getCriteriaOf(Code.class).list();
    }

흠.. 테스트 할 맛이 안난다.

   private Session getSession() {
        return sessionFactory.getCurrentSession();
    }

    private Criteria getCriteriaOf(Class clazz){
        return getSession().createCriteria(clazz);
    }

이런 private 메서드를 쓰고 있지만 전혀 테스트 할만한 뭐시기가 감지되지 않는다.

list 사이즈를 구하는 메서드를 만들어 보기로 했다. 페이징을 구현할텐데 거기서 분명히 전체 목록 크기를 구하는 쿼리가 필요할 것이기 떄문이다.

CodeDao 인터페이스에 코드를 추가하자.

    int totalSize();

다음은 이것을 구현한다.

    public int totalSize() {
        return (Integer)getCriteriaOf(Code.class)
            .setProjection(Projections.count("id"))
            .uniqueResult();
    }

오.. 제법 낯선 코드를 두 중 정도 코딩했다. 왠지 쬐끔 불안하다. 쬐금.. API 학습 차원에서 테스트를 해봐야겠단 생각이 든다. 좋아 결심했어.

소스 코드와 동일한 패키지에 CodeDaoImpleTest라는 테스트를 만든다.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/testContext.xml")
@Transactional
public class CodeDaoImplTest extends DBUnitSupport {

    @Autowired CodeDao codeDao;

    @Test
    public void testTotalSize() throws Exception {
        insertXmlData("testData.xml");
        assertThat(codeDao.totalSize(), is(1));
    }

}

여기서 주목할 것은 굵게 표시한 부분이다. 먼저 테스트에서 사용할 DB가 운영 DB와 같으면 테스트 데이터를 넣고 확인할 때 문제가 될 수 있으니 전혀 다른 DB 설정을 보도록 테스트용 애플리케이션 컨텍스트와 데이터베이스 프로퍼티 파일을 만든다.

    <context:property-placeholder location="classpath*:test.database.properties"/>

이 부분 빼곤 나머진 같다.

database.password=
database.username=sa
database.url=jdbc:hsqldb:mem:ㅇㅇㅇ
database.driverClassName=org.hsqldb.jdbcDriver
hibernate.dialect=org.hibernate.dialect.HSQLDialect

다음은 DBUnit을 사용하기 쉽게 해주는 DBUnitSupport 클래스다. DataSource를 필요로 하기 때문에 자동 주입 받도록 설정한다.

public class DBUnitSupport {

enum DataType {EXCEL, FLATXML}

@Autowired
private DataSource dataSource;

protected void cleanInsertXmlData(String fileSource) throws Exception {
insertData(fileSource, DataType.FLATXML, DatabaseOperation.CLEAN_INSERT);
}
protected void cleanInsertXlsData(String fileSource) throws Exception {
insertData(fileSource, DataType.EXCEL, DatabaseOperation.CLEAN_INSERT);
}

private void insertData(String fileSource, DataType type, DatabaseOperation operation) throws Exception {
InputStream sourceStream = new ClassPathResource(fileSource, getClass()).getInputStream();

IDataSet dataset = null;
if (type == DataType.EXCEL) {
dataset = new XlsDataSet(sourceStream);
}
else if (type == DataType.FLATXML) {
dataset = new FlatXmlDataSet(sourceStream);
}

operation.execute(
new DatabaseConnection(DataSourceUtils.getConnection(dataSource)), dataset);
}

protected void insertXmlData(String fileSource) throws Exception {
insertData(fileSource, DataType.FLATXML, DatabaseOperation.INSERT);
}

protected void insertXlsData(String fileSource) throws Exception {
insertData(fileSource, DataType.EXCEL, DatabaseOperation.INSERT);
}

}

DataSource가 필요하기 때문에 
나머지 코드에 대한 설명은 생략~

<dataset>
<code id="1" name="블랙" code="BLK" />
</dataset>

테스트는 성공한다.

자 이제 DaoTest라는 클래스를 만들어서 설정을 옮겨보자.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/testContext.xml")
@Transactional
public class DaoTest extends DBUnitSupport {
}

참고로 이 클래스를 src 밑에서 만들었다면 컴파일 에러가 난다.

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>org.springframework.test</artifactId>
            <version>${spring.version}</version>
            <scope>test</scope>
        </dependency>

여기서 스코프를 test라고 했기 때문이다. 지워준다. 프레임워크성 코드가 생기기 시작하기 때문에 패키징을 조금 신경써서 나눠둔다.

public class CodeDaoImplTest extends DaoTest {

    @Autowired CodeDao codeDao;

    @Test
    public void testTotalSize() throws Exception {
        insertXmlData("testData.xml");
        assertThat(codeDao.totalSize(), is(1));
    }
}

자. CodeDaoImplTest가 깔끔해졌다. 앞으로 DAO 테스트를 할 땐 이렇게 DaoTest만 확장해서 만들면 되겠다.






top


[봄싹 코딩] DAO 테스트 만들기





지난 KSUG 세미나 때 깜빡하고 못보여드린 내용을 찍었습니다.
외국 서버라 많이 느리네요. ㅠ.ㅠ 일시정지를 눌러두셨다가 한참뒤에 보시기 바랍니다. 흑흑..

국내에도 깔끔하게 광고 없고, ActiveX 설치하지 않아도 되면서, 고화질, 고용량, 장시간 동영상 서비스 좀 제공해주는 곳이 있으면 좋겠습니다.

blip.tv는 다 좋은데 외쿡 서버라 느린다는것만 빼면 정말 완벽합니다.

다운받기
top


[OSAF 테스트 가이드 초안] DAO 통합 테스트

OSAF : 2009.09.01 18:32


어렴풋이 생각은 해봤었는데, 이번 기회에 정리해 보네요. 매우 주관적이며 OSAF 프레임워크를 사용하는 경우에 해당하는 가이드이기 때문에 통용될리는 없다고 생각합니다. 하지만 적어도 저한테는 이렇게 하는 것이 타당해 보이며, 조금만 응용하면, 다른 프레임워크 또는 별도의 프레임워크가 없는 경우에도 어느정도 적절할 것으로 보입니다. 그럼.. 차근차근 정리해 보겠습니다.

DAO 통합 테스트

DAO 테스트는 항상 데이터베이스를 필요로 하기 때문에 통합 테스트로 볼 수 있습니다.

테스트하지 않을 것

  • 프레임워크 코드

OSAF의 GenericDao로부터 상속받은 코드와 하이버네이트 코드는 테스트하지 않습니다.

테스트 할 것

  • 하이버네이트 도메인 모델 검증

도메인 모델에 맵핑 정보가 제대로 설정되었는지 확인합니다.
OSAF GenericDao가 제공하는 save로 도메인 모델을 저장해보는 것으로 확인합니다.

	@Test
public void crud() throws Exception {
Code code = new Code();
code.setCodeCate(CodeCate.CAR_TYPE);
code.setName("BLK");

cd.save(code);
}
  • DAO에서 재정의하는(overriding)하는 addRestrictions 메서드

해당 메서드에서 정의하는 검색 조건과 정렬 옵션에 따른 쿼리가 제대로 동작하는지 테스트 합니다.

	protected Criteria addRestrictions(Criteria c, CodeParams params) {
CriteriaUtils.ilike(c, "name", params.getName(), MatchMode.ANYWHERE);
CriteriaUtils.conditionalEq(c, "codeCate", params.getCodeCate());
c.addOrder(Order.asc("codeCate"));
return c;
}
  • DAO 인터페이스에 별도로 추가한 메서드의 구현체

OSAF에서 상속받지 않은 코드로, DAO 인터페이스에 추가하고, DaoImpl 클래스에서 구현한 메서드도 테스트가 필요합니다.

//DAO 인터페이스
public interface CodeDao extends GenericDao<Code, CodeParams> {
public List<Code> findByCodeCate(CodeCate codeCate);
}

//DAO 인터페이스 구현체
@Repository
public class CodeDaoImpl extends GenericDaoImpl<Code, CodeParams> implements CodeDao{

...

@SuppressWarnings("unchecked")
public List<Code> findByCodeCate(CodeCate codeCate){
return super.getSession().createCriteria(Code.class).add(
Restrictions.eq("codeCate", codeCate)).list();
}

}

테스트 코드 작성 방법

  • 스프링 @Test 선언

애노테이션 추가: DAO 테스트에 스프링 테스트 러너, 설정 파일 위치, 트랜잭션을 설정합니다.

테스트 설정 파일 위치: 테스트용 스프링 설정파일은 test 폴터 밑에 위치한 testApplicationContext.xml을 사용합니다.

트랜잭션 설정: OSAF의 DAO는 트랜잭션 당 세션을 유지하기 위해 getCurrentSession()을 사용하고 있기 때문에 @Transactional을 반드시 추가해야 합니다.

테스트 클래스 이름: 테스트할 DAO 구현체 이름뒤에 Test를 붙여서 작명합니다.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="/testApplicationContext.xml")
@Transactional()
public class CodeDaoImplTest {

}
  • DBUnitSupport 클래스 상속.

DBUnit을 편하게 사용할 수 있도록 DBUbnitSupport 클래스를 상속 받습니다.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="/testApplicationContext.xml")
@Transactional()
public class CodeDaoImplTest extends DBUnitSupport{

}
  • 테스트 데이터를 적성합니다.

XML 테스트 데이터가 필요한 경우 테스트 클래스와 동일한 패키지에 두어 접근성을 높여줍니다.
XML 테스트 데이터는 다음과 같이 작성합니다.

<dataset>
<code id="1" name="test" codeCate="10" />
<code id="2" name="test" codeCate="10" />
<code id="3" name="test" codeCate="20" />
<code id="4" name="test" codeCate="20" />
</dataset>
  • 테스트를 작성합니다.

테스트 데이터가 필요한 테스트는 DBUnitSupport에서 상속받은 insertXmlData();
또는 inserXlsData(); 메서드를 이용하여 테스트 데이터를 넣어줍니다.

	@Test
public void search() throws Exception {
insertXmlData("testData.xml");
CodeParams params = new CodeParams();
OrderPage orderPage = new OrderPage();

params.setName("te");
assertThat(cd.search(params, orderPage).size(), is(4));

params.setName("");
params.setCodeCate(PersistentEnumUtil.valueOf(CodeCate.class, 20));
assertEquals(2, cd.search(params, orderPage).size());
}

생각해볼 것

  • 테스트 데이터베이스와 실제 데이터페이스가 다르다

배포용으로는 MySQL을 사용하면서 테스트할 때는 HSQL을 사용한다. 과연 제대로 테스트 했다고 볼 수 있을까?



top


친구의 DAO 테스트

모하니?/Thinking : 2008.12.16 12:15


올해 여름이었나... 취직 걱정을 하던 친구를 붙잡고 주말마다 스프링 공부를 잠깐 한 적이 있었습니다. 집중하기 힘든 시기였을텐데 나름대로 최선을 다해서 학습을 했었죠. 길게 가진 않았지만 대충 한 두달은 공부한 것 같습니다. 제가 그 당시 그 친구에게 해준 조언은 조급해하지 말고 열공하라는 거였습니다. 결국 지금은 어딘가에 취직해서 2~3달간의 기초 교육 과정을 마치고 현재 첫 프로젝트에서 PL이란 직책을 맡아서 개발을 즐기고 있습니다. 캬캬캬.

블로깅도 꼬드겨서 시켰는데 드문 드문이긴 하지만 꾸준히 하고 있어서 요즘 뭐하고 사는지 대충 알 수 있습니다. 오늘은 오라클 관련 에러 해결한 글이 올라왔길래 뭔가 봤더니 DAO 테스트를 하고 있더군요. 그래서 메신저로 말을 걸어서 물어봤습니다.

오~ DAO 테스트도 해?
DAO 테스트 어떻게 해? DBUnit 써?
테스트 데이터 어떻게 관리해?
테스트 때문에 생기는 찌꺼기 데이터는 어떻게 처리해?

=> 오라클에 테스트용 DB를 하나 만들고 거기에 텍스트 파일로(ex123/기선/자바..) 테스트 데이터 만들고 그거 짤라서 DB에 넣는 유틸 클래스가 있고 테스트 뒤에 찌꺼기 처리하는 유틸 클래스가 있어.

흠.. DBUnit 사용하면 XML이나 Excel로 테스트 데이터 만들면 알아서 DB에 넣어준다는 것과 테스트를 트랜잭션처리 해버리면 찌꺼기 처리할 필요가 없다는 걸 알려주긴 했지만..

그동안 스프링 공부를 안 했는지 DBUnit 도입은 할 거처럼 보이는데 스프링 도입은 망성이고 있네요. 기간이 촉박해서 학습할 시간이 없나봅니다. 그러게 평소에 좀 꾸준히 해두지~ㅋㅋ 모르죠 DBUnit도 귀찮아서 안 쓰고 그냥 지금 하던데로 할지..

어쩄거나 DAO 테스트를 만들고 있다는 사실 자체는 상당히 높이사고 싶네요. 귿좝~
top


Spring MVC 리팩토링 3

모하니?/Coding : 2007.06.22 13:17


이전 글에서 Controller와 Service Layer를 단위 테스트 했습니다. 이번에는 DAO Layer를 단위(?) 테스트 하겠습니다.

이번에는 OASF를 사용하여 DBUnit과 spring-mock.jar에 있는 AbstractTransactionalDataSourceSpringContextTests 를 확장한 클래스를 사용하겠습니다. 토비님께서 만드셨는데 DBUnit 사용하기도 좋고 설정파일들의 위치를 특정 컨벤션만 지켜주면 알아서 읽어들입니다.

하지만 제가 테스트 하는 애플리케이션은 OSAF 설정파일들의 컨벤션을 알지 못했기 때문에;; 그냥 getConfigLocations 를 사용해서 필요한 설정 파일들과 테스트 DB를 사용하도록 합니다. 테스트 DB는 java 6에 내장되어 있는 HSQL을 사용합니다.

먼저 테스트용 데이터를 XML 형식으로 준비합니다.
<dataset>
    <member id="1" name="기선" mail="keesun@mail.com" mobile="111-1111-1111" job="BIT" springAtHome="dd" springAtWork="dd" springModules="dd" attendType="N" />
</dataset>

다음 테스트를 작성합니다.
    public void testFindByMail() throws Exception {
        insertFlatXmlDataSet("test/kr/co/springframework/member/dao/memberData.xml");
        Member member = memberDao.findByMail("keesun@mail.com");
        assertNotNull(member);
        assertNull(memberDao.findByMail(""));
    }

테스트가 통과 하도록 findByMail을 구현합니다.
    @SuppressWarnings("unchecked")
    public Member findByMail(final String mail) {
        List<Member> memberList = getHibernateTemplate().executeFind(new HibernateCallback() {
            public Object doInHibernate(Session s) throws HibernateException, SQLException {
                Query q = s.createQuery("from Member m where m.mail = :mail")
                    .setParameter("mail", mail);
                return q.list();
            }
        });
        if(memberList.size() == 0)
            return null;
        else
            return memberList.get(0);
    }

사용자 삽입 이미지

단위 테스트가 끝났습니다. 이제 남은 건
1. Service Layer에서 통합테스트를 해보고
2. Controller Layer에서 통합테스트를 하면

테스트&구현이 끝이납니다. 그럼 톰캣 돌려서 돌려보면 끝이겠지만 지금도 어느정도 잘 돌아갈 것 같은 자신이 생긴 상태입니다. 나머지는 밥먹고..계속 합니다.
top