본문 바로가기
프로그램 (PHP,Python)

Eclipse에서 Junit 사용법 및 팁

by 날으는물고기 2009. 6. 7.

Eclipse에서 Junit 사용법 및 팁

사용환경은 eclipse 3.2 JDK 1.4.2 , Junit 3.8.1


먼저 해야될 일은 eclipse 에서 java 프로젝트에 junit.jar import 해야한다.

Project 에서 오른쪽 마우스 클릭하여 properties 선택한다.

Java Build Path > Libraries 탭을 선택하고 오른쪽에 Add Exteranl JARS .. 선택하여

그림과 같이 다운받아서 설치해놓은 eclipse 폴더의 junit.jar 링크한다.


다른 방법은 eclipse junit 을 설치 했다면 add Library 클릭해서 나오는 화면에 junit 추가되어있다.
이것을 선택하면된다

현재 2가지의 junit 이 설치되어있는 것이 보일것이다.

추가된화면이다.


이제 testcase 파일을 만들어본다. 마우스를 패키지에 가져가서 오른쪽마우스 클릭 > New

> Junit Test Case 클릭


기본설정된 내용을 그대로 사용한다.

Which method stubs would you like to create? 란은 3가지 함수는 TestCase 클래스의 함수를 상속해서 사용할건지를 묻는 것인데 setUp() 은 클래스 초기화를 할 때 쓰는 함수이며 tesrDown() 은 클래스 종료시에 호출되는 함수이다.


상속하는게 없으면 finish 누르고 끝내지만 아래 그림과 같이 Class under test란에 클래스를

선택하게 되면 함수 리스트가 나오게 되는데 그 함수중 선택을 하게 되면 테스트 케이스는 그것을 실행하게 된다.



선택한 클래스에서 사용할 함수목록들이 나와있다. 선택하고 finish 를 클릭한다.

import junit.framework.TestCase;

 

import org.apache.log4j.Logger;

 

class Fibo{

       private Logger log = Logger.getLogger(this.getClass());

       public int get(int n){

             int result;

             if (n == 1 || n == 2){

                    result = 1;

             }else{

                    result = get(n-1) + get(n-1);

             }

             log.debug(Integer.toString(result));

             return result;

       }

}

public class WorkingStepMainTest extends TestCase {

 

       protected void setUp() throws Exception {

             super.setUp();

       }

       public void testMain(){

             Fibo fibo = new Fibo();

             assertEquals(2, 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));

       }

}


다음은
위의 소스를 실행해본다.


그럼 junit 테스트 perspective 가 나타나면서 결과를 왼쪽에 보여준다.

그림에서 보는 바와 같이 수행시간, 실행한 개수 , 에러 개수. 실패갯수 가 나오며 아래쪽에 Failure Trace 를 보게 되면 실패한 에러내용과 위치가 표시 되어있다, 클릭하게 되면 그 위치로 간다.


아래와 같이 여러 케이스의 함수를 설정해놓고 테스트를 해가면 편리할것이다.

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

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

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


테스트에 쓰이는 함수들은 Assert.class 에 이쓴ㄴ데 그 클래스를 열어보면 아래와 같이

많은 함수들이 있다. 주석으로 설명이 잘 되어있으므로 자기가 테스트하고자 하는 적당한

함수를 골라 쓰면 될것이다.

assertEquals(x, y) : x y 가 같으면 테스트 통과

assertFalse(b) : b false 이면 테스트 통과

assertTrue(b) : b true 이면 테스트 통과

assertNull(o) : 객체 o null 이면 테스트 통과

assertNotNull(o) : 객체 o null 이 아니면 테스트 통과

assertSame(ox, oy)  : ox oy 가 같은 객체를 참조하고 있으면 테스트 통과

assertNotSame(ox, oy) : ox oy 가 같은 객체를 참조하고 있지 않으면 통과

 

public class Assert

{

    protected Assert()

    public static void assertTrue(String message, boolean condition)

    public static void assertTrue(boolean condition)

    public static void assertFalse(String message, boolean condition)

    public static void assertFalse(boolean condition)

    public static void fail(String message)

    public static void fail()

    public static void assertEquals(Object expected, Object actual)

    public static void assertNotNull(Object object)

    public static void assertNull(Object object)

    public static void assertSame(String message, Object expected, Object actual)

    public static void assertNotSame(Object expected, Object actual)

    private static void failSame(String message)

    private static void failNotSame(String message, Object expected, Object actual)

    private static void failNotEquals(String message, Object expected, Object actual)

    static String format(String message, Object expected, Object actual)

}


등등
다른 함수들도 많이 있으니 참고하자.

 

여기서 체크해 봐야할것이 에러를 출력하는 객체가 뭐냐하는것인데 모든 함수는 fail 이라는 함수를 호출하게 되는데 함수의 내용은 다음과 같다.

public static void fail(String message){

        throw new AssertionFailedError(message);

}


AssertionFailedError
객체를 throw 던지게 된다.

객체는 Error 객체를 상속받아 구현된 클래스 인데
 

public class AssertionFailedError extends Error

{

    public AssertionFailedError(){}

    public AssertionFailedError(String message){

        super(message);

    }

}

Error 객체는 다음과 같다. 결국엔 Throwable 상속받은 Error 객체를 쓰는것이다.

이런 식으로 파고 들다보면 설계자의 의도를 알수 있으며 설계는 공인된 구조여서

학습에 많은 도움을 줄뿐만아니라 조금만 응용하면 프로젝트에서 유용하게 사용할수 있을

것이다.

자기가 Error 객체를 하나 만들어 프로젝트에 활용하고 싶다면 위와 같은 패턴으로

Throwable 상속받아 입맛에 맞게 고쳐 적용하면될것이다.

public class Error extends Throwable{

    public Error(){}

    public Error(String s){

        super(s);

    }

    public Error(String s, Throwable throwable){

        super(s, throwable);

    }

    public Error(Throwable throwable){

        super(throwable);

    }

    static final long serialVersionUID = 4980196508277280342L;

}


 

3.8.1 이후 오랜기간 변경되지 않은 JUnit은 4.x대에 이르러 기존과 비교해서 빠른 변화를 보이고 있다. 이 문서에서는 JUnit 4.4에 추가 사항과 팁을 기록한다.

서론

JUnit[http://http://www.junit.org/ JUnit Web site]은 Test Driven Development [1](이하 TDD)의 개념을 정립한 Kent Beck이 직접 작성한 Java Unit Test Frameworks 이다.. Kent Beck은 Test Drivent Development By Example [http://www.amazon.com/Test-Driven-Development-Addison-Wesley-Signature/dp/0321146530 Test Driven Development By Example](이하 TDDBE) 명서로 TDD의 개념을 잘 설명하였다. 이 문서는 TDD에 대한 설명은 TDDBE 같은 좋은책에 맡긴다. 이 문서에서는 JUnit 3.8 버전과 명확한 비교와 더불어 JUnit 4.4 에서 추가된 내용을 서술한다.

  • 모든 소스는 Copy & Paste 만으로 실행할수 있다.

준비

기본적인 시작은 Eclipse에 들어 있는 JUnit이 JUnit 4.1 이므로 http://junit.org/ 에서 JUnit 4.4 이상의 버전을 다운로드 받아서 classpath 상에 위치 시키고 Eclipse에서 자동 추가된 JUnit library는 제거한다.

JUnit 3와 JUnit 4.4 의 비교와 예제

JUnit 3 vs JUnit 4

3.x 4.4
test 작성 TestCase 를 상속받은 객체의 test로 시작하는 함수 @Test 이 붙은 함수
test 실행 Console 용, Swing 용 러너 제공 대부분 IDE에 통합 Console 용 러너 제공, Swing 러너 삭제
(이제 거의 모든 Java IDE에서 JUnit GUI Runner제공)
실행순서 setUp
testXXX
tearDown
@BeforeClass(반드시 static method)
@Before
@Test
@After
@AfterClass(반드시 static method)
assert TestCase 에 정의된 assert 문들 사용
(내부적으로 assert문은 정적 선언되어 있다.)
Assert 에 정의된 정적 assert 문들 사용,
assertThat 이 추가되었다.
기타 test대상 메소드의 객체가 TestCase를 상속받아야함 x

실행 예제

JUnit 3.8을 알고 있는 사람들이 좀더 자세한 내용을 원하면 [http]Get Acquainted with the New Advanced Features of JUnit 4 문서가 도움이 될 것이다.

아래의 예제는 서로 다른 버전이지만, 모두다 JUnit 4.4 에서 잘 돌아간다.

  • JUnit 3.8
import static java.util.Arrays.asList;

import junit.framework.TestCase;

public class SimpleTest extends TestCase {
    private static int number;
    private static Integer staticNumber;

    public void setUp() {
        number = 10;
    }

    public void testSimple() {
        assertEquals(10, number);
        assertEquals(
            asList(new Integer[]{1,2,10}),
            asList(new Integer[]{1,2,number}));
        
        assertTrue(10 == number);    
                
        assertTrue(
            asList(new String[]{"1"}).contains("1"));
        assertTrue("123".contains("1"));
        assertNull(staticNumber);
    }

    public void testDivideByZero() {        
        try {
            assertEquals(0, number / 0);
        } catch (ArithmeticException e) {
            return;
        }
        fail();        
    }
    // 실행 방지를 위해 _ 첨가
    public void _testMultiply() {
        assertEquals(100, number);
    }

    public void tearDown() {
        number=0;
    }
}

  • JUnit 4.4
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.junit.matchers.JUnitMatchers.*;
import org.junit.*;

import static java.util.Arrays.asList;

public class SimpleTest {
    private int number;
    private static Integer staticNumber;

    @BeforeClass
    public static void beforeClass() {
        staticNumber = 10;
    }

    @Before
    public void setUpNumber() {
        number = 10;
    }

    @Test
    public void simple() {
        assertEquals(10, number);
        assertArrayEquals(
            new Integer[]{1,2,10},
            new Integer[]{1,2,number});
        
        assertTrue(10 == number);
        assertTrue(
            asList(new String[]{"1"}).contains("1"));
        assertTrue("123".contains("1"));
        assertNotNull(staticNumber);

        
        assertThat(number,is(10));
        assertThat(
            asList(new String[]{"1"}),hasItem("1"));

        assertThat("123",containsString("1"));
        assertThat(staticNumber,is(notNullValue()));
    }

    @Test(expected = ArithmeticException.class)
    public void divideByZero() {
        assertEquals(0, number / 0);
    }

    @Ignore("not ready yet")
    @Test
    public void multiply() {
        assertEquals(100, number);
    }
    @After
    public void clearNumber() {
        number=0;
    }
    @AfterClass
    public static void afterClass() {
        staticNumber = null;
    }
}


JUnit 4.4 에서 추가된 요소

JUnit 4.4는 초기 4.1에 비해 좀더 명확한 형정의와, 버그 수정 그리고 새로운 인자로 두가지 라이브러리 능력이 일부 포함되었다. [http://junit.sourceforge.net/doc/ReleaseNotes4.4.html Summary of Changes in version 4.4]

assertThat - Hamcrest - library of matchers for building test expressions

2년 전에 Joe Walnes 는 JMock 1에 제약을 표현하기 위해 새로운 assertion 방법을 추가하였는데[http://joe.truemesh.com/blog/000511.html Joe Walnes의 블로그 Flexible JUnit assertions with assertThat()], 이는 읽기 쉬워서[http://weblogs.java.net/blog/tomwhite/archive/2006/05/literate_progra_1.html Literate Programming with jMock] 사람들에게 환영 받았다. 근본적으로, JUnit에서 제공되는 assertEquals 계열의 비교 구문과 기능은 다르지 않지만, 코드 자체가 읽기 편하며, assert 작성시 작성자의 의도를 더 정교하게 표현할수 있다. 이는 실패시 나오는 메세지가 읽기 매우 편하다는 것을 의미한다. 대략의 문법은 다음과 같다.

assertThat([값], [matcher 문법])

이런 jMock의 제약(assert) 관련 Matcher들은 Hamcrest[http://code.google.com/p/hamcrest/wiki/Tutorial Hamcrest - library of matchers for building test expressions] 로 따로 라이브러리가 되고 JUnit 4.4에 포함되었다.

String actual = "A";
assertEquals("A", actual);
이 형태의 코드는 다음과 같이 표현될 수 있다.

String actual = "A";
assertThat(actual , is("A")); 
//That actual is "A"

이런 사용은 코드의 가독성 뿐만이 아니라, 에러코드의 가독성 까지 높일수 있다. 통상 코드 작성시 하게되는 사고는 영어식 어순의 사고인데, 이는 부드럽게 assert를 읽을수 있도록 한다.

다음은 assertThat으로 assert 작성자의 의도를 표현했을때 실패 메세지가 얼마나 명확히 표현되는지 보이는 예제이다.
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.matchers.JUnitMatchers.containsString;
public class SimpleTest {
        String responseString = "color col";
        @Test
        public void usingAssertTrue() {
                assertTrue(responseString.contains("color") && responseString.contains("colour"));
        }
        @Test
        public void usingAssertThat() {
                assertThat(responseString, is(allOf(containsString("color"), containsString("colour"))));
                // That responseString is a containning string "color" and a containning string "colour".
        }
}


TestCase.assertTrue 의 결과


Assert.assertThat 의 결과


JUnit 4.4 에 포함된 Matcher들을 손쉽게 사용하기 위해서, JUnit 4.4에서는 CoreMatcher 와 JUnitMater 속에 라이브러리 상에서 제공하는 대부분의 Matcher들을 static 형태로 포함시켜 두었다.

import static org.hamcrest.CoreMatchers.*;
import static org.junit.matchers.JUnitMatchers.*;

Assumption, Theory Proper library


JUnit 4.4에서 Assume과 @Theory 를 통한 Proper library [http://popper.tigris.org/ Proper Library] 의 능력들이 추가되었다.

Assume 은 기존에 코드 상에서 특정 조건을 만족시키면 실행시키고 싶은 (예~ A서버가 살아 있으면 실행시키고 싶다. ) 가정들을 프레임웍에서 지원하게 되었다.

만약 EXAMPLE_PATH이 존재하면 테스트를 수행한다고 가정하면 기존의 코드는 이런식이다.

public void test1(){
        if(new File(EXAMPLE_PATH).exists() == false)
                return;
        // ... 테스트 코드
}

하지만 JUnit 4.4 에서 Assume 은 다음의 사용을 가능하게 만든다.

@Before
public void before(){
        Assume.assumeThat(new File(EXAMPLE_PATH).exists(), is(true));
}
@Test
public void test1(){
        FileReader fr = new FileReader(EXAMPLE_PATH);
        // ... 테스트 코드
}

내부적으로 Assume.assumeThat은 org.junit.Assume.AssumptionViolatedException 예외를 던지며, 이는 아무 메세지 없고 실패, 성공 내용이 없는 예외이다. 현재 Eclipse나 기타 IDE에 addon된 JUnit들은 @Test내에서 이를 쓰면 실패로 취급하며, @Before에서 사용하면 정상적인 동작을 한다.

Assume 은 @Theory 와 더불어, 여러 가정을 만들어서 여러 Test값을 컨트롤 할수 있는 능력을 가질수 있는데, 이는 JUnit 4.4에 포함되었지만 org.junit.experimental 에 포함되어 있으므로, 차후 4.5 이후에 사용법이 명확해 지면 더 기술하도록 하겠다. Release Note[http://junit.sourceforge.net/doc/ReleaseNotes4.4.html JUnit ReleaseNote 4.4]의 예제를 기반으로 장난감 수준의 코드를 작성해 보았다.

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.assertThat;
import static org.junit.Assume.assumeThat;
import static org.junit.matchers.StringContains.containsString;
import org.junit.experimental.theories.DataPoint;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.experimental.theories.suppliers.TestedOn;
import org.junit.runner.RunWith;
@RunWith(Theories.class)
public class TheoryExample {
        public class User {
                private String username;
                public User(String username) {
                        this.username = username;
                }
                public String getUsername(){
                        return this.username;
                }
        }
        @DataPoint public static String GOOD_USERNAME = "optimus";
        @DataPoint public static String USERNAME_WITH_SLASH = "optimus/prime";
        @Theory
        public void filenameIncludesUsername(String username) {
                assumeThat(username, not(containsString("/")));
                assertThat(new User(username).getUsername(), containsString(username));
        }
        @SuppressWarnings("unchecked")
        @Theory
        public void multiplyIsInverseOfDivideWithInlineDataPoints(
                        @TestedOn(ints = { 0, 5, 10 }) int amount,
                        @TestedOn(ints = { 0, 1, 2 })  int m) {
                assumeThat(m, not(0));
                assertThat(amount*m/m, is(amount));
        }
}

JUnit 팁

JUnit 4 에서는 TestCase를 만들기는 쉽다. 그런데 TestSuite 어느 문서에 나온거야?

JUnit 3.8 에서는 TestSuite 상속받은 AllTest에 TestCase를 상속 받은 클래스들을 추가하는 형태로 묶음을 만들었다.

public class AllTests {
        public static Test suite() {
                TestSuite suite = new TestSuite("Test for default package");
                //$JUnit-BEGIN$
                suite.addTestSuite(SimpleTest.class);
                //$JUnit-END$
                return suite;
        }
}


package test;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses( { SimpleTest.class,SimpleTest2.class})
public class AllTests {
}

재미있게도, JUnit 4가 나온지 꽤 지났지만, 이 방법은 JUnit FAQ[http://junit.sourceforge.net/doc/faq/faq.htm#running_15 How do I organize all test classes in a TestSuite automatically and not use or manage a TestSuite explicitly?]에 기술되어 있지 않으며 @RunWith의 [http]JavaDoc에만 그 사용법이 적혀 있다. FAQ에서는 Ant를 이용한 자동화나, JUnit을 도와주는 라이브러리는 이용한 자동화로 실행하기를 권장한다. TestCase가 많아질수록 위와 같이 명시적으로 Suite를 직접 만들어 쓰면, 테스트 실행을 누락할 수 있는 위험이 있다.

static import가 귀찮다.

Eclipse에 범용적으로 쓸수있는 유용한 방법이다.

JUnit 4.x 의 @Test 에서 유효성 검사(Assert)를 하기 위해서는 Assert.assert***를 static import 하는 필수이다.

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
...
assertThat("A", is("A");
...

그런데, static import의 불편한 점은 문제는 Eclipse에서 이들을 사용할때 추천(Intellisense)를 해주지 않는 다는 불편함이 있다.

Eclipse 3.3에 추가된 Ctrl+3으로 favorite 을 검색해보면 다음의 설정화면을 만날수 있다. 이곳에서 추천(intellisense)시 검색에 포함할 static import 를 지정시킬수 있다.


여기에서 주의할 점은, JUnit 4.4는 기존 버전과 호환성을 위해서 org.framework.Assert 포함하고 있으므로 반드시 org.junit.Assert를 선택한다. 이렇게 세팅해 놓으면 특별한 import 없이도 다음과 같이 사용할수 있다.


Eclipse에서 곧 바로 JavaDoc을 보고 싶다.

Eclipse에 범용적으로 쓸수있는 유용한 방법이다.

Eclipse에서 F2키는 해당 API의 JavaDoc 을 Eclipse 가 랜더링해서 보여준다. 그런데, Shift+F2를 하면, 지정된 JavaDoc을 Eclipse가 최우선하는 웹브라우저에서 보여준다. JDK나 JRE로 세팅된 sun사의 공식 JavaDoc에 미리 연결되어 있다. 지금 당장 다음을 소스를 작성한다음

String example = "example";

String 위치에서 Shift+F2 를 누르면, 웹브라우저에서 Sun사에 JavaDoc을 구경할수 있다.

이 기능을 모른다면, 우리는 JavaDoc 찾기 위해 그 위치를 다음신이나 구글신에게 의존할 수 밖에 없다. 그런데, 대부분의 오픈소스 라이브러리 특히 apache commons[http://commons.apache.org/ Apache Commons] 같은 JDK 수준으로 쓰이는 라이브러리들은 프로젝트 홈페이지에서 JavaDoc을 제공한다. Eclipe에서는 library를 classpath에 세팅하는 과정에서 해당 라이브러리와 JavaDoc을 연결하는 옵션을 제공한다.


http://junit.sourceforge.net/javadoc_40/index.html

이렇게 세팅해두면, Shift+F2 를 이용해서 해당 라이브러리의 JavaDoc을 웹상을 뒤질필요 없이 곧 바로 볼수 있다.

만약, 웹상에 JavaDoc을 제공하지 않고, 따로 JavaDoc을 제공한다면, 이를 다운 받아 zip형태로 묶은 다음 프로젝트에 일정 공간에 포함시켜 그림의 Javadoc in archive로 포함시키면, Eclipse를 스스로 서버를 띄워서 제공한다. 좌측 옵션에 볼수 있듯이 Java Source Attachment를 통해서 소스도 링크 시켜 둘수 있다. (Apache Commons를 사용하면서 특히나 유용하였다.)

위의 @RunWith 를 이용한 TestSuite 작성 방법도 이 세팅후에 JavaDoc을 둘러 보는 과정에서 알 수 있었다.

Eclipse에서는 이 외에도 수많은 유용한 세팅들이 있는데, 많은 경우 사용하지 않는 것 같다. 차후 기회가 마련되면 다시 언급해보겠다.

결론

JUnit 4.4에서 새롭게 도입된 assertThat 을 써보면서, 가독성의 증가를 느끼고 있다. 더불어, 불만이었던 assert의 표현력에 증가를 체감할 수 있었다.

아직 JUnit에는 jMock처럼 풍부한 Matcher들을 모두 포함 되지는 않았다. 차후 버전에서 합의된 더 많은 수의 Matcher의 지원이 기대된다. (아직도 JUnit 메일리 리스트[http://tech.groups.yahoo.com/group/junit/ Yahoo Groups JUnit] 활기를 띄는 점이 놀랍다.) 그리고 @Theory 를 통한 더 손쉬운 테스트를 기대한다.


출처 : http://dna.daum.net/
728x90

댓글