JUnit에서 setUp() 또는 @Before를 사용하는 이유?

참조: http://stackoverflow.com/questions/512184/best-practice-initialize-junit-class-fields-in-setup-or-at-declaration

JUnit은 각각의 테스트를 독립적으로 테스트하기 위해 테스트 마다 개별적인 객체를 생성합니다. 따라서..

public class SomeTest extends TestCase
{
   private final List list = new ArrayList();

    public void testPopulateList()
   {
       // Add stuff to the list
       // Assert the list contains what I expect
   }
}

이런식으로 테스트를 작성해도

public class SomeTest extends TestCase
{
   private List list;

    @Override
   protected void setUp() throws Exception
   {
       super.setUp();
       this.list = new ArrayList();
   }

    public void testPopulateList()
   {
       // Add stuff to the list
       // Assert the list contains what I expect
   }
}

이렇게 작성한 것과 동일하게 각각의 테스트를 실행하기 전에 초기화 하는 코드를 실행합니다.

그럼 대체 setUp이나 @Before는 왜 사용할까요? 그냥 생성자를 사용하거나 저렇게 필드에 직접 객체를 생성하게 해도 비슷한데 말이죠.

윗 글을 찾아보기 전에는 ‘그냥 tearDown이랑 균형을 맞출려고 만들었나?’, ‘생성자를 쓰면 안 좋은 뭔가가 있나?’ 등등 막연하게 생각하고 있었습니다. 그러다가 도무지 궁금해서 구글신에게 문의했더니 윗 글을 찾을 수 있었습니다.

댓글들을 조사해 본 결과.

1. Exception 발생: setUp()에서 예외를 던지면 JUnit이 유용한 스택 트레이스 정보를 돌려주지만, 생성자에서 예외가 발생하면 그냥 테스트 객체를 못 만드는 것이기 때문에 유용한 정보를 얻을 수 없음.

2. 베스트 프랙티스: 테스트 하려는 클래스의 인스턴스는 테스트 또는 setUp()에서 생성하고, 테스트 대상이 아닌 속성들은 필드에서 직접 new로 생성해도 상관없다.

이 두 가지가 유력해 보이네요.

Why OSAF 1. 테스트 코드를 익힐 수 있습니다.

OSAF를 공개한지 한 달이 아직 안 됐습니다. 10월 23일에 공개했었죠. 지금까지 약 200에 가까운 다운로드를 기록하고 있지만, 전혀… 아무런… 반응이 없다는 것에는 가히 놀라울 뿐입니다. 그냥 제가 쓴 글의 댓글 몇 개 정도 뿐의 관심이 저에게 한 편으로는 아쉬움으로 또 다른 한 편으로는 오기로 다가옵니다.

완전 최첨단 프레임워크인 OSAF에 왜 이렇게 관심이 없을까. 고민을 많이 했습니다. 어렵나? 메이븐 떄문인가? 그거 없어도 되는데. 문서가 부족하긴 부족하고. 그래도 어떻게 이렇게 조용할 수가 있지. 홈피 디자인이 좀 구리긴 한데.. 그거 때문인가? ㅋ. OSAF 발음이 너무 어려운가? 별에 별 생각을 많이 했습니다. 당연히 기운도 빠집니다. OSAF를 공개한 건 어쩌면 OSAF에게 못씁짓을 한 건 아닌지 말이죠.(Max님의 ‘어디가서 밥은 먹고 다녀야 할텐데..’ 라는 댓글이 생각납니다.)

긍정적으로 생각하기로 했습니다. 언젠가는 빛을 보겠지. 열심히 계속 가꾸다 보면 언젠간 알아주겠지. 하고 말이죠. 그래서 OSAF가 여러분에게 어떤 도움을 줄 수 있을지 생각하고 알려드리기로 했습니다. 그 중 첫 번째가 바로 테스트 코드입니다.

OSAF의 테스트 커버리지는 60%가 조금 넘습니다. (앞으로 차차 올릴 예정입니다.) 60%의 테스트 커버리지는 전부 OSAF 개발팀에서 직접 작성한 테스트 코드입니다. 어딘가에서 배껴온 코드가 절대로 아닙니다. 테스트는 초기에 JUnit과 EasyMock을 사용해서 작성 했었습니다. 물론 스프링 테스트 기능도 사용하고 있죠. 배포 직전에는 EasyMock을 Mockito로 교체하여 비슷한 테스트를 보다 깔끔하고 직관적이며 적은 수의 코드로 대체할 수 있었습니다. DBUnit을 확장하여 OSAF가 제공하는 테스트 케이스를 이용하면 DAO 테스트가 매우 간편해질 것 입니다.

이렇게 좋은데… 한 번 들여다 보고 뭐라고 해주시지 않으시겠어요? 좋다. 잘했다. 고맙다. 이런거 말구요. 이 부분의 테스트는 이해가 안 된다. 테스트가 조금 이상하다. 이 부분의 테스트는 왜 안했냐. 어려워서 그런거냐? 이 부분의 테스트는 이렇게 고치는게 좋치 않겠냐? 이런.. 반응이 제가 가장 좋아하는 반응이자 OSAF에게 거름을 주는 방법입니다.

소스 코드는 굳이 다운 받지 않아도(장기적으론 받아 두시면 좋겠지만..)

http://www.opensprout.org:9060/browse/OSAF/osaf/trunk

위 링크로 가시면 웹에서 직접 볼 수 있습니다. 소스 코드나 OSAF 와 관련하여 문제나 제안하고 싶은 것이 있다면 주저하지 마시고 이슈를 등록해 주세요.

http://www.opensprout.org/jira/secure/Dashboard.jspa

JUnit 공부하자.

코딩하기
TDDBE에 있는 xUnit 예제 코드 자바로 코딩하기.
TDD – xUnit 18장.
TDD – xUnit 19장 (2)
TDD – xUnit 20장
TDD – xUnit 21장
TDD – xUnit 22장
TDD – xUnit 23장

코드보기

1.0 코드는 구하기 힘듬. 하지만 구했음 캬캬. 테스트 코드 분실.
2.0 코드의 가장 큰 변화는 “test”가 앞에 붙은 녀석들을 자동으로 등록/실행.
3.0 코드의 가장 큰 변화는 Assert가 상위 클래스가 됨.

개념파악
http://www.martinfowler.com/bliki/Xunit.html <- 기원
http://www.martinfowler.com/bliki/JunitNewInstance.html <- 왜 Isolate 시켰는가?

junit doc에 있는 cooktour 읽을 것.

Kent Beck 한테 배우는 거다. JUnit에 담겨 있는 패턴과 깔끔한 코드를…

Ant 빌드로 테스트 실행하기

참조 : http://www.java2s.com/Code/Java/Ant/Junittest.htm

1. JUnit jar 파일을 Ant의 lib 폴더에 추가

먼저 junit.jar 나 junit-4.4.jar 파일을 ANT_HOME의 lib 폴더에 넣어줍니다.  이 일도 태스크로 작성할 수 있는데, 굳이 이 일을 타겟으로 지정해서 할 필요는 없을 것 같아서 뺏습니다.

2. 빌드 작성

빌드 파일을 작성합니다. 제가 작성한 테스트의 경우에는 xml파일이나 프로퍼티 파일을 필요로 하기 때문에 해당 파일들도 build 디렉터리로 복사해주는 작업이 prepare에 추가됐습니다.

<property name=”dir.build” value=”build” />
    <property name=”dir.test” value=”test” />
    <property name=”dir.src” value=”src” />

    <!– JUnit –>
    <target name=”prepare”>
        <mkdir dir=”${dir.build}” />
        <copy file=”${dir.test}/memberData.xml” todir=”${dir.build}” failonerror=”true” />
        <copy file=”${dir.test}/testDatabase.properties” todir=”${dir.build}” failonerror=”true” />
    </target>

    <target name=”compile” depends=”prepare” description=”Compile all source code.”>
        <javac srcdir=”${dir.test}” destdir=”${dir.build}”>
            <classpath refid=”base-classpath” />
        </javac>
        <javac srcdir=”${dir.src}” destdir=”${dir.build}”>
            <classpath refid=”base-classpath” />
        </javac>
    </target>

    <target name=”junit” depends=”compile”>
        <junit printsummary=”on” fork=”false” haltonfailure=”false” failureproperty=”tests.failed” showoutput=”true”>

            <classpath refid=”base-classpath” />
            <formatter type=”brief” usefile=”false” />

            <batchtest>
                <fileset dir=”${dir.test}”>
                    <include name=”**/*Test.java” />
                </fileset>
            </batchtest>
        </junit>

        <fail if=”tests.failed”>
        *******************************************************
        *******************************************************
        One or more tests failed. Check the output…
        *******************************************************
        *******************************************************
        </fail>
    </target>

3. 빌드파일 실행하기
사용자 삽입 이미지사용자 삽입 이미지

JUnit 으로 Hibernate 테스트

지난번에는 PizzaApp 클래스에서 새로운 피자 객체를 만들고 save()를 사용해서 DB에 insert를 했었습니다.

이번에는 PizzaTest 클래스를 만들고 JUnit을 사용해 봅니다. save()를 사용해서 DB에 피자 한판을 넣어보고 들어갔는지 빼내어 보고 빼낸 것이 null이 아닌지 확인해 봅니다.

연습용이기 때문에 test가 별로 맘에 안드실 수도 있습니다. 저도 별로 테스트가 맘에 들지 않지만 DB에서 데이타를 꺼내오고 JUnit 형태로 만들어 보는것에 목적을 뒀습니다.

public class PizzaTest {


 


       private Session s;


 


       private Transaction tx;


 


       @Before


       public void createSession() {


             ApplicationContext ac = new ClassPathXmlApplicationContext(


                           new String[] { “applicationContext-dao.xml”,


                                        “applicationContext-jdbc-datasource.xml” });


             SessionFactory sf = (SessionFactory) ac.getBean(“sessionFactory”);


 


             s = sf.openSession();


             tx = s.beginTransaction();


       }


 


       @Test


       public void createPizza() {


             Pizza p = new Pizza();


             p.setName(“keesun’s 2007 pizza”);


             p.setPrice(2007);


             p.setToping(“happiness”);


             p.setSize(“2007cm”);


 


             s.save(p);


 


             List pizzaList =  s.createQuery(“from O_Pizza”).list();


             assertNotNull(pizzaList);


       }


 


       @After


       public void close() {


             tx.commit();


             s.close();


       }


}


오.. 잘 돌아갑니다. O_Pizza는 실제 table에는 o_pizza 이렇게 소문자로 들어가 있습니다. postgres 특징인것 같습니다. 하지만 프로그램에서도 o_pizza 이렇게 쓰면 다음과 같은 에러를 볼 수 있습니다.


[#M_ more.. | less.. |
org.hibernate.hql.ast.QuerySyntaxException: o_pizza is not mapped [from o_pizza]
 at org.hibernate.hql.ast.util.SessionFactoryHelper.requireClassPersister(SessionFactoryHelper.java:158)
 at org.hibernate.hql.ast.tree.FromElementFactory.addFromElement(FromElementFactory.java:87)
 at org.hibernate.hql.ast.tree.FromClause.addFromElement(FromClause.java:70)
 at org.hibernate.hql.ast.HqlSqlWalker.createFromElement(HqlSqlWalker.java:257)
 at org.hibernate.hql.antlr.HqlSqlBaseWalker.fromElement(HqlSqlBaseWalker.java:3056)
 at org.hibernate.hql.antlr.HqlSqlBaseWalker.fromElementList(HqlSqlBaseWalker.java:2945)
 at org.hibernate.hql.antlr.HqlSqlBaseWalker.fromClause(HqlSqlBaseWalker.java:688)
 at org.hibernate.hql.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:544)
 at org.hibernate.hql.antlr.HqlSqlBaseWalker.selectStatement(HqlSqlBaseWalker.java:281)
 at org.hibernate.hql.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:229)
 at org.hibernate.hql.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:228)
 at org.hibernate.hql.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:160)
 at org.hibernate.hql.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:111)
 at org.hibernate.engine.query.HQLQueryPlan.<init>(HQLQueryPlan.java:77)
 at org.hibernate.engine.query.HQLQueryPlan.<init>(HQLQueryPlan.java:56)
 at org.hibernate.engine.query.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:72)
 at org.hibernate.impl.AbstractSessionImpl.getHQLQueryPlan(AbstractSessionImpl.java:133)
 at org.hibernate.impl.AbstractSessionImpl.createQuery(AbstractSessionImpl.java:112)
 at org.hibernate.impl.SessionImpl.createQuery(SessionImpl.java:1623)
 at model.PizzaTest.createPizza(PizzaTest.java:43)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
 at java.lang.reflect.Method.invoke(Unknown Source)
 at org.junit.internal.runners.TestMethodRunner.executeMethodBody(TestMethodRunner.java:99)
 at org.junit.internal.runners.TestMethodRunner.runUnprotected(TestMethodRunner.java:81)
 at org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34)
 at org.junit.internal.runners.TestMethodRunner.runMethod(TestMethodRunner.java:75)
 at org.junit.internal.runners.TestMethodRunner.run(TestMethodRunner.java:45)
 at org.junit.internal.runners.TestClassMethodsRunner.invokeTestMethod(TestClassMethodsRunner.java:71)
 at org.junit.internal.runners.TestClassMethodsRunner.run(TestClassMethodsRunner.java:35)
 at org.junit.internal.runners.TestClassRunner$1.runUnprotected(TestClassRunner.java:42)
 at org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34)
 at org.junit.internal.runners.TestClassRunner.run(TestClassRunner.java:52)
 at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:38)
 at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)_M#]

DB의 table이름을 쓰지 말고 @Entity의 name속성에 지정한 이름으로 사용해야 합니다.