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
클래스 다이어그램
클래스의 의미는 일반적으로 객체지향 언어에서 사용하는 클래스의 의미와 유사하다. 클래스라는 것은 시스템에서 동작하게 되는 하나의 개념의 추상화 도구로써 사용되며 추상화의 단계에 따라 클래스의 의미가 약간씩 차이가 생긴다. 만약 설계 당시에 추상화가 아주 높은 단계에서 이러한 클래스는 시스템에서 사용되는 하나의 역할로서의 의미를 가진다. 하지만 구현단계와 같은 추상화 단계가 아주 낮은 상태에서는 실제 객체를 생성하기 위한 클래스의의미를 가지게 된다. 이러한 단계의 구별은 사용자의 의도에 따라 적당히 사용하면 될 것이다.
상속의 의미는 일반 언어에서의 상속의 의미와 유사하게 상위클래스의 모든 특징과 행위를 하위의 클래스가 모두 이어받게 된다. 즉 다양한 클래스들의 나열에서 동일한 행위나 특징을 가진 여러 클래스들이 존재할 때 공통되는 부분을 상위 클래스로 만들 수 있다.
4.클래스와 클래스의 연관관계(Association Relationship)
의존관계의 의미는 한 클래스의 변화가 다른 클래스에 영향을 미칠 때 사용한다. 이러한 의존의 관계는 여러가지 관계에서 나타날 수 있다. 의존관계의 종류에 관하여서는 다음 호에 자세히 다루도록 할 것이다.
유스케이스(Usecase) 다이어그램
유스케이스의 표기는 그림 1에서와 같이 타원으로 표시하고 이름을 속에 명시하게된다.
그림 2 - 액터
그림 3 - 스테레오 타입이 액터인 클래스
그림 4 -액터와 액터 사이의 일반화 관계
그림 5 - 통신(Association) 관계
그림 6 - 확장(Extends) 관계
그림 7 - 사용(Uses) 관계
그림 8 - 일반화(Generalization) 관계
- 시스템의 주기능을 사용하는 사람은 누구인가.
- 누가 시스템으로부터 업무 지원을 받는가?
- 누가 시스템을 운영, 유지 보수하는가?
- 시스템과 정보를 교환하는 외부 시스템은 무엇인가?
- 시스템이 내어놓은 결과물에 누가 관심을 가지는가?
- Actor가 요구하는 시스템의 주요 기능은 무엇인가?
- Actor가 시스템의 어떤 정보를 수정, 조회, 삭제, 저장하느가?
- 시스템이 Actor에게 주는 어떠한 Event가 있는가?, Actor가 시스템에는 어떠한 Event가 있는가?
- 시스템의 입력과 출력으로 무엇이 필요한가? 그리고 입력과 출력이 어디에서 오고 어디에로 가는가?
- 시스템의 구현에서 가장 문제가 되는 점은 무엇인가?
참고로 유스케이스 다이어그램을 잘 그리기 위해 다음의 단계로 넘어가는 것을 주저하지 말기 바란다. 프로젝트를 잘 수행하기 위해서는 여러 번의 반복적 개발을 통해 오류의 수정 과정이 필요하고 이에 유스케이스 다이어그램을 수정하는 일도 포함된다. 즉 어느 정도 유스케이스 다이어그램이 완성되면 다음의 다이어그램을 진행하길 바란다.