My work space/Java

JUnit 이클립스 사용

schpritz 2008. 8. 20. 15:12
JUnit 이클립스 사용

XUnit

XUnit의 X는 변수이다. 자바에는 JUnit이 있고 C++에는 CppUnit이 있으며 Python에는 PyUnit이라는 것이있다. 모두 각 언어에서 유닛테스트를 쉽게 해 줄 수 있는 도구이다. 필자는 이 곳의 예제를 가장 대중적인 언어인 자바로 선정했고 이곳에서 JUnit에 대해 잠시 살펴보고 넘어가도록 하자.

JUnit

JUnit은 자바 프로그래밍 시 Unit테스트를 쉽게 해주는 프레임 워크로 TDD의 창시자라고도 할 수 있는 Kent Beck과 디자인 패턴 책의 저자인 Erich Gamma에 의해서 작성되었다.

JUnit은 단 하나의 jar파일로 구성되어 있으며 사용법이 매우 간단한 것이 그 특징이라고 할 수 있겠다. 이 곳에서는 JUnit의 기본적인 사용법과 요새 자바 IDE로 크게 인기를 끌고 있는 Eclipse에서의 JUnit사용법을 함께 알아보자.

우선 실제로 JUnit을 어떻게 사용하는지 간단한 예제를 통해서 알아보자.

junit.jar파일은 http://www.junit.org 에서 다운로드 할 수 있으며, 만약 이클립스 사용자라면 plugin디렉토리에 디폴트로 설치가 되어 있는 것을 볼 수 있을 것이다.

이전에 작성했던 피보나치 수열을 Junit을 이용하여 재구성하면 다음과 같은 코드가 만들어지게 된다.

FiboTest.java <PRE>import junit.framework.TestCase;class Fibo { public int get(int n) { if (n==1 || n==2) return 1; return get(n-2)+get(n-1); }}public class FiboTest extends TestCase { public static void main(String[] args) { junit.textui.TestRunner.run(FiboTest.class); } public void testFibo() { Fibo fibo = new Fibo(); assertEquals(1, fibo.get(1)); assertEquals(1, fibo.get(2)); assertEquals(fibo.get(1)+fibo.get(2), fibo.get(3)); assertEquals(fibo.get(2)+fibo.get(3), fibo.get(4)); assertEquals(55, fibo.get(10)); }}</PRE>

FiboTest클래스는 main메써드의 junit.textui.TestRunner.run(FiboTest.class)를 호출함으로써 테스트가 진행된다. Junit은 FiboTest라는 클래스의 메써드중 test로 시작하는 이름의 메써드는 테스트 메써드로 자동인식하고 자동으로 실행을 시킨다. (java의 reflection을 이용한 방법이다.) 따라서 test로 시작하는 메써드가 10개라면 10개의 테스트 메써드가 실행될 것이다.

우리는 이전에 Fibo라는 클래스에 assertSame이라는 메써드를 직접 만들어서 테스트시 사용했었다. 하지만 junit을 이용하면 우리가 작성했던 assertSame과 동일한 역할을 하는 assertEquals라는 메써드(TestCase클래스의 메써드)가 존재한다. assertEquals메써드 역시 기대값과 결과값이 일치하는지를 조사해주는 역할을 담당한다.

정상적으로 junit.jar를 클래스패스에 등록해주고 위 프로그램을 실행하면 에러없이 테스트가 수행되는 것을 확인할 수 있다. 만약 테스트를 일부러 실패하도록 다음과 같이 수정하고 프로그램을 실행하면,

assertEquals(2, fibo.get(1));

다음과 같은 상세한 Trace를 구경할 수 있다.

<PRE>junit.framework.AssertionFailedError: expected:<2> but was:<1> at junit.framework.Assert.fail(Assert.java:47) at junit.framework.Assert.failNotEquals(Assert.java:282) at junit.framework.Assert.assertEquals(Assert.java:64) at junit.framework.Assert.assertEquals(Assert.java:201) at junit.framework.Assert.assertEquals(Assert.java:207) at FiboTest.testFibo(FiboTest.java:18) … 이하생략</PRE>

우리가 이전에 만들었던 assertSame메써드와 마찬가지로 expected : <2> but was : <1>라는 실패 원인에 대해서 친절하게 알려주고 있다.

이클립스에서 junit을 사용하기 위해서는 다음과 같은 절차를 밟아야 한다.

  1. 프로젝트 생성
  2. 프로젝트 Properties선택
  3. Java Build Path선택
  4. Add External JARs선택
  5. plugins / org.junit_3.8.1 / junit.jar 선택
  6. OK 선택

다음은 위와 같은 순서를 진행한 후의 필자의 이클립스 Package Explorer의 모양이다.

Java Project명은 tdd로 했고 junit.jar가 포함되어 있는 것을 확인할 수 있다. 새로운 TestCase(FiboTest.java)를 추가하기 위해서는

이클립스 메뉴의 위 버튼을 클릭하고 Junit TestCase를 선택하면 된다. 보통 테스트 클래스명은 테스트할 클래스명+Test로 하는 것이 일반적이다. 우리는 Fibo클래스를 테스트 할 것이므로 FiboTest로 하였다. (위 FiboTest.java참조)

새로운 FiboTest클래스를 생성하였다면 FiboTest.java를 위와 같이 타이핑하고 실행해보자. 실행은 이클립스의 다음 버튼을 누르고

Run As a Junit Test를 선택하면 된다. (한번 실행 후 단축키 Ctrl-F11을 눌러서 재실행할 수 있다.)

테스트를 실행하면 다음과 같은 결과를 볼 수 있을 것이다.

소요된 시간은 0.01 seconds이며 총 1개의 테스트중 1개가 실행되었고 Error는 0, Failures는 0임을 알려준다. 그리고 진행바는 초록막대기로 표시가 된다. 초록막대기의 의미는 테스트가 성공했음을 알려주는 표시이다.

만약 테스트를 일부러 실패하도록 코드를 다음과 같이 수정하고 테스트를 수행하면

<PRE>assertEquals(2, fibo.get(1));</PRE>

테스트는 실패하게 되고 다음과 같은 결과를 볼 수 있다.

제일 먼저 눈에띄는 것은 빨간 막대기로 이것은 테스트가 실패했음을 알려준다. 자세히 보면 Failures가 1로 바뀌었음을 알 수 있다. 또한 실패한 테스트 메써드명(testFibo)이 무엇인지 알려주고 있다.

Junit에서 Failure와 Error의 의미는 다음과 같이 구별된다. <PRE>Failure : 테스트의 기대값과 결과값이 틀린경우Error : 테스트 수행시 오류발생, NullPointerException과 같은 RuntimeError일 경우 발생한다.</PRE>

테스트가 실패한 경우에는 빨간 막대기가 있는 화면의 하단부분에 실패에 대한 Trace정보가 아래와 같이 표시된다.

이클립스는 Junit에 대한 준비가 잘 되어있는 훌륭한 IDE로 많은 자바 프로그래머들의 사랑을 받고 있다.

우리는 지금껏 junit의 TestCase 메써드중 assertEquals만을 살펴 보았는데 assertEquals외에도 여러 유용한 메써드들이 많이 있다. 이중에서도 가장 많이 사용되는 메써드들을 간단하게 알아보도록 하자.

<PRE>assertEquals(A, B)</PRE>

assertEquals는 A와 B가 일치하는지를 조사한다. A나 B에는 Object, int, float, long, char,boolean,,,등의 모든 자료형이 들어갈 수 있다. 단 A,B의 타입은 언제나 같아야만 한다.

<PRE>assertTrue(X)</PRE>

X가 참인지를 조사한다. X는 boolean형태의 값이어야 한다.

<PRE>assertFalse(X)</PRE>

X가 거짓인지를 조사한다. assertTrue와 정 반대의 메써드라 보면 되겠다. 역시 X는 boolean형태의 값이어야 한다.

<PRE>fail(message)</PRE>

테스트가 위 문장을 만나면 message를 출력하고 무조건 실패하도록 한다. 위 메써드는 주로 예외상황을 테스트하거나 아직 테스트가 끝나지 않았음을 명시적으로 나타내주기 위해 자주 사용되곤 한다.

[예외상황 테스트의 예] <PRE>try { userMethods.run(parameter.bad()); fail("should not reach here!"); }catch(UserException e) { assertEquals(-1, e.getErrorCode());}</PRE>

userMethods.run이라는 메써드에 임의로 비정상적인 파라미터를 입력했을 때 UserException이 꼭 발생해야 한다는 것을 의도하는 테스트이다. 만약 UserException이 발생하지 않는다면 fail문 때문에 테스트가 실패하게 되는 것이다.

<PRE>assertNotNull(Object X)</PRE>

X가 Null이 아닌지를 조사한다. 만약 Null이라면 assertionFailedError가 발생한다.

<PRE>assertNull(Object X)</PRE>

X가 Null인지를 조사한다. 만약 Null이 아니라면 assertionFailedError가 발생한다.

<PRE>assertSame(Object A, Object B)</PRE>

A와 B가 같은 객체인지를 조사한다. (주의: 우리가 Fibo클래스에서 직접 만들었던 assertSame과는 전혀 다른의미임)

이정도가 junit으로 테스트코드를 만들 때 가장 많이 사용하게 될 메써드가 될 것이다.

setUp & tearDown

이제 곧 여러분도 경험하게 되겠지만 테스트를 작성하다 보면 하나의 메써드로 모든걸 테스트할 수는 없게된다. 따라서 테스트 메써드의 숫자도 계속해서 증가해 나갈수 밖에 없는데 각각의 테스트 메써드가 공통적으로 사용하는 것을 매번 중복해서 적고 있는 자신을 발견하게 될 것이다.

setUp, tearDown메써드는 test로 시작하는 메써드와 마찬가지로 junit에서 자동으로 인식하는 메써드명이다. setUp메써드는 test로 시작하는 메써드가 수행되기 직전에 호출되고 tearDown메써드는 test로 시작하는 메써드가 종료된 직후에 호출된다.

setUp과 tearDown메써드를 적절히 활용하면 test로 시작하는 메써드들간의 중복을 제거할 수 있을 뿐만 아니라 각각의 테스트의 독립성을 보장할 수 있게 된다. 테스트의 독립성은 매우 중요한 이슈인데 하나의 테스트는 다른 테스트에 의해서 영향을 받지 않아야 함을 뜻한다. 만약 testB라는 메써드가 testA라는 메써드가 수행된 이후에 수행되어야 한다면 그 테스트는 벌써 독립성이 깨져버린 불안한 테스트가 되어 버리는 것이다.

[setUp메써드의 예] <PRE>import junit.framework.TestCase;class Fibo { public int get(int n) { if (n==1 || n==2) return 1; return get(n-2)+get(n-1); }}public class FiboTest extends TestCase { Fibo fibo; public static void main(String[] args) { junit.textui.TestRunner.run(FiboTest.class); } public void setUp() { fibo = new Fibo(); } public void testFibo() { assertEquals(1, fibo.get(1)); assertEquals(1, fibo.get(2)); } public void testFibo2() { assertEquals(fibo.get(1)+fibo.get(2), fibo.get(3)); assertEquals(fibo.get(2)+fibo.get(3), fibo.get(4)); assertEquals(55, fibo.get(10)); }}</PRE>

위에서 보았던 FiboTest클래스를 위와 같이 구성하여도 동일한 결과가 나온다. fibo객체를 setUp메써드에서 미리 생성해주고 그 이후에 testFibo, testFibo2메써드가 수행되도록 한 것이다. 위와같이 setUp메써드를 구성하면 test로 시작하는 각각의 메써드에서 fibo객체를 만들 필요가 없다. setUp에서 이미 생성되기 때문이다.(물론 testFibo에서 사용했던 fibo객체와 testFibo2에서 사용한 fibo객체는 다른 것이다.)


출처 : http://wiki.tdd.or.kr/wiki.py?TddTutorial.JunitTutorial