2008. 10. 1. 16:26

JUnit 을 이용한 단위 테스트




유난히도 "버그"를 많이 발생시키는 프로그래밍 패턴때문에, 난 "버그양성소" 역할을 해왔었다. 그동안은 main() 메소드를 통해 클래스와 메소드를 테스트했는데, 그동안의 무식함을 반성하고자 제일 기본적인 JUnit에 대해서 알아보았다.

1. JUnit 이 뭐지?
JUnit 은 테스트 프레임워크 (툴) 이다.
한마디로, 정해진 형식에따라 test 메소드를 작성해 놓으면 자동으로 실행시켜주고 결과도 리턴해주는 놈이다.

그럼 기존 java 애플리케이션 [public static void main(String[] args)] 을 이용한 테스트와는 뭐가 다른가?
정해진 방법대로 작성해 놓으면  테스트 java 애플리케이션 안의 메소드를 호출하지 않아도 자동 실행해주기 때문에, 손이 덜 간다. 그리고, 테스트 Java 애플리케이션이 많을때 이것을 일일이 실행해야 테스트를 해야하는 반면, JUnit은 TestSuit 이라는 개념을 지원해 여러개의 Test 클래스들을 한꺼번에 실행해 볼수 있다.

2. JUnit 을 사용하려면 ?
다운로드 및 참고자료 : http://junit.sourceforge.net/

junit.jar 를 classpath에 추가 하기만 하면 된다.
Eclipse 에는 기본 플러그인으로 설치되어 있어 바로 사용가능하다.

3. 코드 작성법
  1) JUnit의 TestCase 를 상속하여 클래스를 생성한다.
  2) 메소드 실행전 초기화 부분은 setUp() 메소드에, 실행후 초기화는 tearDown() 에 기술한다.
  3) 각각의 테스트할 단위들을 testXXXXX() 로 작성한다.
  4) 각 메소드에 assertXXXX() 메소드를 삽입하여, 테스트의 성공 유무를 판가름한다.
      아래 조건을 만족하지 않으면 실패로 간주
         ex) assertEquals  : 같지 않으면 실패,

assertEquals - 같은지 비교
assertNull - null값을 리턴하는지 비교
assertNotNull - 인자로 넘겨받은 객체가 null인지 판정하고 반대인경우 실패로 처리한다.
assertSame - assertSame 은 expected 와 actual이 같은 객체를 참조하는지 판정하고
                       그렇지 않다면 실패로 처리한다.
assertNotSame - expected 와 actual이 서로 '다른' 객체를 참조하는지 판정하고, 만약
                       같은 객체를 참조한다면 실패로 처리한다.
assertTrue - boolean 조건이 참인지 판정한다. 만약 조건이 거짓이라면 실패로 처리한다.
fail - 테스트를 바로 실패 처리한다.

  
4. 코드샘플과 실행결과
   1) 코드 샘플

import junit.framework.TestCase;
import com.ilikeclick.fw.util.*;

public class testUtilClass extends TestCase
{
    private String testStr1 = null;
    private String testStr2 = null;
      
    public void setUp()
    {
        System.out.println("setUp()");
    }
   
    public void tearDown()
    {
        System.out.println("tearDown()\n");
       
        testStr1 = null;
        testStr2 = null;
    }
   
    public void testCryptUtility()
    {
        System.out.println("testCryptUtility()");
       
        testStr1 = "sokum";
        testStr2 = CryptUtility.encrypt(testStr1, 3);
       
        assertNotNull(testStr2);
        assertEquals(testStr1, CryptUtility.decrypt(testStr2, 2));
    }
   
    public void testRandomUtility()
    {
        System.out.println("testRandomUtility()");       
       
        for(int loopCnt=0; loopCnt < 128; loopCnt++)
        {
            testStr1 = RandomUtility.createNumAlphaCode(loopCnt);
           
            assertNotNull(testStr1);
        }
    }
}


  2) 콘솔 - 실행결과

사용자 삽입 이미지


 








  3) JUnit 결과창
사용자 삽입 이미지



위에서는 testCryptUtility() 에서 테스트가 실패하게 비밀키를 다른게 설정했다. 참고로, CryptUtility 는 시져알고리즘을 구현한 클래스로 비밀키를 이용하여 암호화와 복호화를 지원한다.

JUnit 결과창을 보면, 실행갯수와 오류와 실패 갯수, 실행시간, 실패에 대한 추적을 한눈에 볼수 있다.

  4) 테스할 Class에 대한 TestCase 코드 자동생성하기

  이클립스에서, 테스트하고 싶은 클래스에 마우스 오른쪽 버튼을 누르고 JUnit 테스트 코드를 생성하면, 선택된 클래스의 모든 메소드를 테스트 가능하도록 껍질코드(?)를 만들어 준다.

  5) TestCase 한꺼번에 실행하기

  이러한 TestCase 를 한대에 묶어서, TestSuit 을 만들어 한꺼번에 실행해 볼 수 있다. 추가로 테스트할 TestCase 는 // $JUnit-BEGIN$ 과 // $JUnit-END$ 사이에 클래스이름을 추가해주면 된다.

import junit.framework.Test;
import junit.framework.TestSuite;

public class AllTests
{
    public static Test suite()
    {
        TestSuite suite = new TestSuite("Test for com.ilikeclick.fw.test");
        // $JUnit-BEGIN$
        suite.addTestSuite(testUtilClass.class);
        // $JUnit-END$

        return suite;
    }
}

5. JUnit 을 적용하면 좋은점
  1) Eclipse에 기본으로 플러그인이 포함되어 있어 사용이 쉽다.
  2) JUnit 은 단위테스트의 개념을 초기확립(?) 시킨 놈으로써의 의의와 범용적으로 사용하고 있다.
  3) TestSuit 을 제공함으로써 여러개의 TestCase 클래스를 한번에 실행시킬수 있다.

6. 불편한 점
  1) 메소드 실행전후에 초기화를 담당하는 setUp(), tearDown() 메소드에 사용자 파라메터를 넘길수 없어
      불편하다.
  2) 반드시 TestCase 를 상속해야 하기땜에, 단일상속의 제한이 있는 Java 에서는 다양한 테스트가 불가능
     하다. +_+ 제일 불편한 부분이 아닐까 싶다.
     물론, JUnit 이 다른 클래스나 다른 모듈에 의존성 없이독립적으로 실행되어야 한다는 점은 십분 이해가
     가지만, 그렇다고 확장성마저 없애버린것은 좀 오버인듯 싶다.
     (그래서 다른 테스트 플랫폼도 나오고 있지만...)
  3) Servlet 은 어떻게 테스트하지? +_+;; 물론, 더 찾아봐야 알겠지만 Servlet은 어떻게 해야 할지 감이 안온다.

* JUnit 을 둘러보며 느낀점
  1) 결국 JUnit 은 형태를 정해놓고, 그 방법대로 코드를 작성하면 자동으로 메소드를 실행해주는 역할 밖에
      하지 않을정도로 간단한 놈이다.
  2) 라이브러리를 지원하지 않아도 Reflection 을 사용하면 금방 구현할것 같다. 결국 어떤사람이 먼저
     아이디어를 떠올리고 그것을 실행으로 옮기느냐에 따라 선도자가 될수도 있고, 흐름에 따르는 순응자가
     될수도 있다는 것을 다시 한번 느꼈다.
  3) 버그가 줄어들겠지? 가 아니라, 테스트 메소드를 호출하기에 편할것같다.  
     버그 날만한 것을 testXX() 메소드에 기술하여 테스트하는건 기존과 똑같으므로 결국 프로그래머에 달렸다.
     단, 누가 보더라도 메소드의 의도가 명확하고, 모든 메소드에 대하여 testXXX() 메소드를 반드시 작성해야
     함으로 작성자가 아닌 다른 프로그래머가 해당 메소드를 사용할때 "샘플코드" 로써의 역할도 수행할 수 있
     어 편할 듯 싶다.

예전 프로젝트에서 공통업무를 맡으면서 수많은 버그들을 만들어, 본인이나 쓰는 사람이나 서로 스트레스를 많이 받았었다. 이젠 개발 5년차다. 그만큼 단단한 코드 작성이 필요한 시점이 아닐까 싶다.

작성       : 남경식 / 2008-01-02 / isokum@hotmail.com