SpringDAO를 이용하면 Hibernate를 이용하는 DAO를 쉽게 만들 수 있다. 핵심은 HibernateTemplate클래스를 이용한 template방식의 접근이며 DataSource DI가 가능하고 template접근이 용이하게 설계된 HibernateDaoSupport를 상속해서 쓰는 것이 일반적이다.

이런 DAO를 테스트 할 때 문제는 DAO라는 것이 그 본질상 Unit Test가 어렵다는 데 있다. 그 이유는 DAO라는 것이 결국 백그라운드에서 돌아가는 persistent 기능을 이용하는 stub과 같은 존재이기 때문에 Unit Test보다는 DB가 참여하는 Integration Test로 작성하는 것이 상식처럼 되어있다. 물론 DB액세스는 데이터준비의 어려움과 테스트 성능저하 등의 부가적인 문제를 가지고 있다. 그래서 Embedded DB갈은 것을 이용한 Fake DB를 사용하는식의 방법을 많이 이용한다. JDBC자체를 Unit Test하기 위한 몇가지 툴이 존재하기는 하는데 사용하기 불편하며 사실 SQL이라는 문장으로 주로 정보가 넘어가기 때문에 그 자체를 비교평가하기가 만만치 않다.

결국 많은 개발자들은 DAO테스트는 integration test로 해야한다는 것을 당연하게 받아들인다.

하지만 static한 query를 날리는 DAO말고 다이나믹한 query를 생성하거나 data logic등을 가지고 있는 DAO라면 Unit Test를 쓰고 싶은 욕심이 생길 수도 있다. Hibernate는 오래된 텍스트 기반의 SQL 또는 그와 유사한 HQL을 사용하지 않고 Criteria라는 OO기반의 Query object를 지원한다. Query의 모든 요소를 object형태로 표현할 수 있기 때문에 dynamic query를 작성하기에 잘 맞으며 OO적이기 때문에 ORM의 구조와도 잘 연결된다. Object로 표현된 모델정보를 다시 HQL같은 텍스트로 표현해 놓으면 당장 리팩토링할때마다 텍스트를 다 찾아서 일일히 수정해줘야 하는 JDBC/Raw SQL을 쓸 때의 문제점에서 크게 벗어날 수 억다. 하지만 Criteria는 그런 면에서 월등하다. 그래서 Hibernate를 좋아하는 개발자들 중에는 그 선호하는 이유가 Criteria때문이라고 얘기하는 사람이 많다. 나도 그런 사람중의 하나이다. 특히 Hibernate 2.x에서는 Criteria가 HQL로 표현할 수 있는 모든 것을 다 표현하지 못해서 불편한 점이 많았는데 3.x에 와서는 거의 대부분의 기능을 다 Criteria를 써서 (심지어 Projection까지) 표현할 수 있기 때문에 더욱 애용되고 있다.

Criteria를 사용하는 코드를 Unit Test한다고 해보자. Criteria는 Session에서 만들어지는데 Unit Test를 하기 위해서라면 그 Session을 mocking하는 것이 필수이다. 하지만 코드에서 Session을 직접가져오는 구조라면 매우 어려울 수 밖에 없다. 하지만 Spring Dao에서는 Template을 이용한 Callback방식을 사용하고 Session을 그 Callback method로 전달해주는 구조로 되어있다. 따라서 이를 잘 활용하면 쉽게 DB와 연동 없이 Unit Test를 만들 수 있다.

간단히 다음과 같은 SpringDao기반의 Hibernate DAO코드가 있다고 치자

[java]
public class DaoImpl extends HibernateDaoSupport {
 public List findEntityByName(final String name) {
  return (List)getHibernateTemplate().execute(new HibernateCallback() {
   public Object doInHibernate(Session s) throws HibernateException, SQLException {
    Criteria c = s.createCriteria(Entity.class);
    c.add(Restrictions.eq(“name”, name));
    
    return c.list();
   }});
 }
 …
}
[/java]
findEntityByName이라는 메소드는 내부적으로 anonymous inner class방식으로 HibernateCallback인터페이스를 구현한 callback을 만들고 이를 HibernateTemplate의 execute로 전달한다. 내부에서 이미 시작된 Hibernate session이 있으면 이를 사용하고 없다면 새로운 session을 오픈한 뒤 이를 callback의 파라메터로 전달한다. 이 메소드를 unit test하려면 일단 HibernateTemplate을 먼저 mocking해야 한다. 다행히 HibernateDaoSupport는 HibernateTemplate의 setter를 제공한다. 다만 HibernateTemplate은 인터페이스가 아니고 클래스이므로 proxy를 만들거나 MockClass를 쉽게 만들 수가 없다. 방법은 HibernateTemplate을 상속해서 필요한 메소드를 override하거나 아니면 CGLIB등을 쓰는 방법이 있다. 테스트를 위해 만들어야 하는 메소드는 HibernateTemplate의 execute()이다. 내부적으로 Session을 만들어서 callback의 doInHibernate를 호출해주면 된다. 테스트하고자 하는 핵심은 결국 조건에 따라서 Criteria의 어떤 method를 호출하는 가를 검증하는 것이다. 주로 add(Criterion)메소드에 대해서 검증하는 방법을 쓰면 된다. 그러려면 Criteria를 mock으로 만들어야 한다. 하지만 Criteria는 직접 전달할 수는 없고 파라메터로 넘어가는 Session의 createCriteria를 이용해서 코드 내부에서 생성을 한다. 결국 Session, Criteria두개를 다 mock으로 만들어줘야 한다.

Callback의 내부코드가 실행되는 것을 테스트 해야하기 때문에 좀 복잡해진다. 물론 Callback을 독립적인 클래스로 만들면 별도로 테스트가 가능하겠지만 보통 anonymous inner class를 쓰기 때문에 hibernatetemplate부터 시작해서 3단계의 mock이 필요하다.

내가 만들어 본 것은 두가지 방법을 썼는데 첫째는 직접 클래스와 인터페이스를 이용해서 Mock클래스를 만들어서 쓴 것이다. Manual 방식으로 만들기에 적합한 케이스 중의 하나이다. 이렇게 만들어서 테스트 해본 것은 OSAF의 GenericHibernateDao에 적용을 했었다.

오늘은 이것을 EasyMock을 써서 한번 구현해봤다. EasyMock2.2에는 IAnswer라는 인터페이스를 써서 Mock이 리턴하는 값을 파라메터에 따라서 임의로 조종하게 하는 것이 가능하다. Mock으로 만든 HibernateTemplate의 execute를 실행할때 파라메터로 넘어오는 callback을 그 IAnswer를 구현한 클래스 내에서 직접 호출해주는 코드를 작성하고 그 파라메터로 역시 EasyMock으로 생성한 Session, Criteria의 Mock을 사용하면 된다. 그러면 간단한 Easymock코드를 통해서 callback내부의 코드에 대한 unit test가 가능해진다. 한가지 문제점은 Criteria에 조건으로 넣는 Criterion기반의 SimpleExpression들을 파라메터 비교를 해야하는데 이 바보같은 Hibernate는 SimpleExpression의 비교를 할 수 있는 equals를 구현해 놓지 않았다. 따라서 두개의 Restrictions.add(“a”,”b”)라는 것을 비교하면 다르다고 나와버린다. -_-. 그래서 찾은 방법은 toString을 이용하는 것인데 Hibernate의 scope of object identity개념을 생각하면 entity를 파라메터로 줘도 동일한 클래스이므로 identity비교만으로 두개의 비교가 가능하다. 이를 위해서 파라메터 matching을 위한 간단한 EasyMock 파라메터비교 클래스를 만들었다. SimpleExpression비교는 Hibernate에 이슈로 올려야겠다.

아무튼 그렇게 작성한 코드는 다음과 같다. HibernateTemplate을 mocking하기 위해서 EasyMock class extension을 함께 사용했다.

[java]
package caps.sample.test;

import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.getCurrentArguments;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;

import java.util.ArrayList;
import java.util.List;

import org.easymock.IAnswer;
import org.easymock.IArgumentMatcher;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.SimpleExpression;
import org.junit.Before;
import org.junit.Test;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.HibernateTemplate;

public class DaoImplTest {
 DaoImpl daoImpl;
 HibernateTemplate mockHTem;
 
 @Before public void setup() {
  daoImpl = new DaoImpl();
  mockHTem = org.easymock.classextension.EasyMock.createMock(HibernateTemplate.class);
  daoImpl.setHibernateTemplate(mockHTem);
 }
 
 @Test public void testCriteria() {
  org.easymock.classextension.EasyMock.expect(mockHTem.execute((HibernateCallback)anyObject()))
   .andAnswer(new HibernateTemplateExecute());
  
  org.easymock.classextension.EasyMock.replay(mockHTem);
  
  List list = daoImpl.findEntityByName(“Toby”);
  assertEquals(1, list.size());
  
  org.easymock.classextension.EasyMock.verify(mockHTem);
 }
 
 static class HibernateTemplateExecute implements IAnswer {
  public Object answer() throws Throwable {
   HibernateCallback hc = (HibernateCallback)getCurrentArguments()[0];
   Session s = createMock(Session.class);
   Criteria c = createMock(Criteria.class);
   List expected = new ArrayList();
   expected.add(new Entity(“Toby”));
   expect(s.createCriteria(Entity.class)).andReturn(c);
   expect(c.add((Criterion)eqSimpleExpression(Restrictions.eq(“name”,”Toby”)))).andReturn(c);
   expect(c.list()).andReturn(expected);

   replay(s);
   replay(c);
   
   assertSame(expected, hc.doInHibernate(s));
  
   verify(c);
   verify(s);
   
   return expected;
  }
 }
 
 static SimpleExpression eqSimpleExpression(SimpleExpression se) {
  reportMatcher(new SimpleExpressionMatcher(se));
  return null;
 }
 
 static class SimpleExpressionMatcher implements IArgumentMatcher {
  SimpleExpression se;
  SimpleExpression realse;
  
  public SimpleExpressionMatcher(SimpleExpression se) {
   this.se = se;
  }

  public void appendTo(StringBuffer buf) {
   buf.append(“expected ” + se.toString() + ” but ” + realse.toString());
  }

  public boolean matches(Object o) {
   if (!(o instanceof SimpleExpression)) return false;
   realse = (SimpleExpression)o;
   return se.toString().equals(realse.toString());
  }
 }
 
}
[/java]

원하는 테스트는 성공적으로 잘 수행된다. EasyMock의 위력과 편리함을 잘 보여주는 케이스이다.

다만 테스트할 케이스가 많다면 코드가 불필요하게 길어질 것 같다.

그래서 Criteria를 사용하는 Hibernate Callback코드를 쉽게 테스트할 수 있는 Mock클래스를 하나 개발해야겠다는 생각을 하게됐다. Criteria에 조건을 어떤 순서로 넣었는지는 상관없이 파라메터에 따라 몇개의 restriction이 추가되고 모드나 추가 Criteria가 생성이 되었는지 쉽게 체크할 수 있는 그런 Mock을 하나 만들어봐야겠다.

사람들은 이래서 저래서 Unit Test만들기가 어렵다고 한다. 또는 어떤 경우들은 불가능하다고 한다. 하지만 내 경험으로는 Unit Test를 못만들 것은 없다. JUnit Recipe를 보면서 더 강하게 느낀 점이다. Unit Test에 안되는게 어딨니!

update: 그나저나 이 놈의 WordPress는 내 코드의 generic type부분을 태그로 생각해서 다 짤라먹어버렸군 -_-. 제대로 된 code를 표현할 수 있는 블로그로 바꾸고 싶다. ㅜㅜ

Related posts:

  1. spring과 hibernate 함께 쓰기. 그 만만치 않은 작업.
  2. AppFuse DAO Test 코드의 문제점
  3. Hibernate 3.0 released!
  4. Hibernate 3.2 GA 출시
  5. Spring+Hibernate+Eclipse로 .NET을 이기다
  6. Spring보다 많이 팔린 Seam, Wicket
  7. Hibernate In Action
  8. Hibernate 성능 & 최적화
  9. 학습테스트(Learning Test)를 이용해서 공부하기
  10. Spring 3.0 (19) Test 모듈의 선택라이브러리 분석
  11. Spring과 Google Guice의 협력을 통한 DI 애노테이션 표준화 작업 시작
  12. Hibernate vs. SpringFramework Again
  13. Hibernate Tips
  14. 테스트 코드에서 static import를 편하게 넣는 방법
  15. Spring Expressions(SpEL)를 이용한 Mockito Argument Matcher 만들기

Facebook comments:

to “Spring기반의 Hibernate DAO Unit Test 만들기”

  1. Your style is very unique in comparison to other folks I’ve read stuff from. I appreciate you for posting when you have the opportunity, Guess I will just bookmark this web site.

  2. There as certainly a great deal to learn about this subject. I really like all the points you have made.

  3. I think this is a real great blog post.Thanks Again. Fantastic.

  4. Howdy! This post couldn’t be written any better! Looking through this article reminds me of my previous roommate! He constantly kept preaching about this. I’ll forward this information to him. Pretty sure he’ll have a great read. Thanks for sharing!

  5. Excellent blog you’ve got here.. It’s hard to find high quality writing like yours these days. I really appreciate people like you! Take care!!

  6. I’m excited to discover this great site. I wanted to thank you for ones time due to this wonderful read!! I definitely enjoyed every bit of it and i also have you saved to fav to look at new stuff on your web site.

  7. This blog was… how do I say it? Relevant!! Finally I’ve found something which helped me. Many thanks!

  8. Greetings! Very helpful advice within this post! It’s the little changes that make the biggest changes. Thanks a lot for sharing!

  9. Im no pro, but I consider you just crafted the best point. You certainly know what youre talking about, and I can seriously get behind that. Thanks for staying so upfront and so truthful.

  10. This is a topic which is near to my heart… Cheers! Exactly where are your contact details though?

  11. Good post. I learn something totally new and challenging on websites I stumbleupon everyday. It’s always interesting to read through articles from other writers and use something from their web sites.

  12. I really like it when folks get together and share opinions. Great blog, keep it up!

  13. bookmarked!!, I like your web site!

  14. This page definitely has all of the info I wanted concerning this subject and didn’t know who to ask.

  15. This site was… how do you say it? Relevant!! Finally I have found something which helped me. Thanks a lot!

  16. I needed to thank you for this fantastic read!! I definitely loved every little bit of it. I have you saved as a favorite to look at new things you post…

  17. I love looking through an article that will make men and women think. Also, thank you for allowing for me to comment!

  18. I must thank you for the efforts you have put in writing this blog. I’m hoping to see the same high-grade content by you later on as well. In fact, your creative writing abilities has motivated me to get my very own website now ;)

  19. the time to read or check out the subject material or websites we have linked to below the

  20. Spot on with this write-up, I actually feel this amazing site needs a great deal more attention. I’ll probably be returning to read through more, thanks for the advice!

  21. check beneath, are some entirely unrelated internet sites to ours, nevertheless, they may be most trustworthy sources that we use

  22. Nice post. I learn something new and challenging on sites I stumbleupon on a daily basis. It will always be interesting to read through articles from other authors and use something from their web sites.

  23. This excellent website really has all the information and facts I needed concerning this subject and didn’t know who to ask.

  24. Thanks so much for the article post.Much thanks again. Much obliged.

  25. Wonderful story, reckoned we could combine a few unrelated information, nonetheless definitely worth taking a appear, whoa did one learn about Mid East has got more problerms also

  26. bookmarked!!, I love your site!

  27. I was able to find good advice from your blog articles.

  28. usually posts some extremely intriguing stuff like this. If you are new to this site

  29. I want to to thank you for this fantastic read!! I certainly loved every bit of it. I have got you book-marked to check out new stuff you post…

  30. Everything is very open with a precise description of the issues. It was really informative. Your website is useful. Thanks for sharing!

  31. Good post. I learn something new and challenging on sites I stumbleupon everyday. It’s always interesting to read through articles from other authors and practice something from other sites.

  32. Really appreciate you sharing this blog.

  33. I really love your website.. Pleasant colors & theme. Did you develop this website yourself? Please reply back as I’m hoping to create my own site and want to learn where you got this from or just what the theme is named. Kudos!

  34. It’s hard to come by experienced people on this topic, but you sound like you know what you’re talking about! Thanks

  35. very couple of internet sites that occur to become in depth below, from our point of view are undoubtedly nicely worth checking out

  36. Excellent post. I will be facing a few of these issues as well..

  37. The information talked about within the post are several of the best offered

  38. I have to thank you for the efforts you have put in penning this website. I’m hoping to view the same high-grade blog posts by you later on as well. In fact, your creative writing abilities has encouraged me to get my very own website now ;)

  39. Thanks for the sensible critique. Me & my neighbor were just preparing to do a little research about this. We got a grab a book from our local library but I think I learned more clear from this post. I’m very glad to see such magnificent information being shared freely out there.

  40. bookmarked!!, I really like your website!

  41. This is a topic that is near to my heart… Best wishes! Where are your contact details though?

  42. check below, are some entirely unrelated internet sites to ours, nevertheless, they’re most trustworthy sources that we use

  43. You ought to take part in a contest for one of the highest quality websites on the internet. I’m going to recommend this site!

  44. Pretty! This has been an extremely wonderful article. Thanks for supplying these details.

  45. Good post. I learn something totally new and challenging on blogs I stumbleupon every day. It will always be helpful to read articles from other authors and use something from their websites.

  46. this this web site conations in fact pleasant funny data

Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

© 2017 Toby's Epril Suffusion theme by Sayontan Sinha