JUnit 단계별 설명

2007. 4. 11. 14:54

JUnit 개발자

JUnit 단계별 설명

저자:Michel Casabianca

JUnit 테스트 사례 작성

이제까지 자신이 구현한 IsoDate가 잘 만들어졌는지를 확인해 보기 위한 테스트 클래스 소스를 살펴 보았습니다. 그러면 이제 그러한 테스트 파일을 실제로 구현하는 것에 대해 살펴보기로 하겠습니다.

테스트는 JUnit 프레임워크의 이점을 이용할 수 있도록 JUnit.framework.TestCase로부터 값을 계승하며, 이 클래스 이름은 "Test"라는 말이 덧붙여진 테스트 클래스의 이름이 됩니다. 예를 들어 IsoDate라는 클래스를 테스트하고 있다면, 테스트 클래스 이름은 IsoDateTest가 됩니다. 일반적으로 이 클래스는 개인적인 것들 이외의 모든 메소드에 액세스 할 수 있도록, 테스트 클래스와 동일한 패키지에 들어 있습니다.

클래스 내에서 테스트할 각 메소드에 대한 메소드를 하나씩 작성해야 한다는 것에 유념하십시오. 예를 들어, ISO 데이타 형식을 사용하는 생성자나 메소드를 테스트할 경우에는, ISO 형식의 String을 인수로 하고 toString()을 메소드로 취하는 생성자에 대한 테스트 메소드를 작성해야 할 것입니다. 이름을 짓는 패턴은 테스트 사례의 경우와 유사합니다. 즉, 테스트된 메소드(또는 생성자)의 이름 앞에 "test"를 추가하기만 하면 됩니다.

테스트 메소드의 본문은 assertion을 검증함으로써 테스트 대상 메소드를 시험합니다. 예를 들어 toString() 실행에 대한 테스트 메소드에서 여러분은 초기 시작(UNIX 시스템이 탄생한 1970년 1월 1일 자정)이 메소드에 의해 잘 포맷되어있는지 검사하기를 원할 수 있습니다. 이 assertion을 실행하려면, 프레임워크가 제공하는 assertion 메소드를 사용하면 됩니다. 이들 메소드는 프레임워크의 JUnit.framework.Assert 클래스에서 실행할 수 있으며 테스트에서 액세스할 수 있는데, Assert가 TestCase의 부모 클래스이기 때문입니다. 이들 메소드는 assert Java 키워드(J2EE 1.4의 최신 버전)에 필적하는 것입니다. Assertion 메소드에는 기본 유형들 (부울, 정수 등) 사이의 동등성 또는 객체 사이의 동등성을 검사하는 (()equals 메소드를 사용하여 두 객체가 동일한 것인지 검사함) 메소드들이 있습니다. 다른 assertion 메소드는 두 객체의 동일성 여부를 검사합니다. 즉, 객체가 널(null)인지 아닌지, 그리고 (일반적으로 표현식에서 비롯되는) 부울이 참인지 거짓인지 등을 검사합니다. 이들 메소드는 표 1에 요약되어 있습니다.

부동(float)의 또는 이중 인수(double argument)로 작업하는 assertion에 대해서는, 비교에 허용되는 델타를 인수로 취하는 세 번째 메소드가 있습니다. assertEquals() 및 assertSame() 메소드는 일반적으로 동일한 결과를 만들어내지 않는다는 점에도 유의하십시오. (동일한 값을 가진 두 개의String 이라 할지라도, 서로 다른 메모리 주소를 가진 별개의 객체로서, 동일한 것이 아닐 수도 있습니다.) 그러므로, assertEquals() 는 assertion를 검증하는 반면, assertSame()은 그렇게 하지 못합니다. 표 1 의 각 assertion 메소드에 대해서는, assertion이 실패할 경우, 설명적인 메시지를 제공하는 다른 인수를 포함시킬 수 있습니다. 따라서, 예를 들자면 , assertEquals(int expected, int actual) 은 assertEquals(String Message, int expected, int actual)과 같은 메시지와 함께 사용될 수 있습니다.

Assertion이 실패할 경우, assertion 메소드는 AssertFailedError 이나 ComparisonFailure를 발생시킵니다. AssertionFailedError는 java.lang.Error로부터 계승되므로, 테스트 메소드의 throws 절에 이를 선언할 필요는 없습니다. ComparisonFailure 는 AssertionFailedError로부터 계승되므로, 이것 역시 선언할 필요가 없습니다. Assertion 실패 시 테스트 메소드에 오류가 발생하게 되므로, 후속 assertion은 수행되지 않습니다. 프레임워크는 이 오류들을 잡아낸 다음 테스트가 실패한 것으로 간주하여, 오류 메시지를 인쇄합니다. 이 메시지는 assertion으로부터 생성되어, (있을 경우) assertion 메소드에 전달됨으로써 완성됩니다.

메소드의 끝에 다음과 같은 행을 넣어 보겠습니다

assertEquals("This is a test",1,2);

Now compile the test and run it:

$ javac *.java
$ java junit.textui.TestRunner IsoDateTest
.F.
Time: 0,348

There was 1 failure:
1) testIsoDate(IsoDateTest)junit.framework
.AssertionFailedError: This is a test expected:<1> but was:<2>
      at IsoDateTest.testIsoDate
      (IsoDateTest.java:29)

FAILURES!!!
Tests run: 2,  Failures: 1,  Errors: 0

JUnit은 각각의 처리된 테스트를 위해 점을 인쇄해 주고 실패한 경우에는 F로 표시되고, 실패한 assertion을 위해 메시지를 표시합니다. 이 메시지는 assertion 메소드에 보내진 설명과 (자동으로 생성되는) assertion 결과로 구성됩니다. 여기에서는 assertion 메소드에 대한 인수의 순서가 생성된 메시지를 이해하는 데 매우 중요한 것임을 알 수 있습니다. 첫번 째 인수는 기대 값인 반면 두 번 째 것은 실제 값이기 때문입니다.

테스트용 메소드에 잘못(예를 들어 예외 발생 등)이 발견되면, 툴에 의해 (assertion 실패에서 비롯된 실패가 아니라) 오류로서 표시됩니다. 이제 IsoDateTest 클래스를 수정하여, 전에 추가했던 행을 다음 행으로 바꾸어 보겠습니다.

throw new Exception("This is a test"); 

이제 테스트를 컴파일하고 수행해 보겠습니다.

$ javac *.java

$ java junit.textui.TestRunner IsoDateTest 
.E.
Time: 0,284
There was 1 error:
1) testIsoDate(IsoDateTest)java.lang.
   Exception: This is a test at IsoDate
   Test.testIsoDate(IsoDateTest.java:30)

FAILURES!!!
Tests run: 2,  Failures: 0,  Errors: 1

툴은 예외를 오류로 표시하기 때문에, 오류는 테스트 실행이 손상되었다기 보다는 테스트 메소드가 손상되었음을 의미합니다.

Assert 클래스는 AssertionFailedError을 발생시켜 테스트 실행을 인터럽트하는, fail() 메소드 (그리고 설명 메시지가 있는 버전)을 포함하고 있습니다. 이 메소드는 Assertion 메소드 호출 없이 테스트 실패를 유도하려 할 때 유용하게 쓰입니다. 예를 들어, 하나의 코드가 예외를 발생시켜야 함에도 불구하고 그렇게 하지 않을 경우, 다음과 같이 fail() 메소드를 호출함으로써 테스트 실패를 유도할 수 있습니다.

public void testIndexOutOfBounds() {
  try {
       ArrayList list=new ArrayList();
       list.get(0);
       fail("IndexOutOfBoundsException   
           not thrown");
  } catch(IndexOutOfBoundsException e) {}
}

Junit의 고급 기능

샘플 테스트의 경우 여러분은 여러 테스트를 동시에 실행해 왔을 것입니다. 그러나 실제 상황에서는 구현 방식을 검사하기 위해 하나의 특정한 테스트 방법을 사용하는 것을 선호하기 때문에, 사용할 일련의 테스트를 정의할 필요가 있습니다. 이것이 junit.framework.TestSuite 프레임워크 클래스의 목적입니다. 이 프레임워크는 단순히 일련의 테스트를 추가할 수 있는 컨테이너에 불과합니다. toString() 구현을 위한 테스트 방법을 사용하고 싶다면 suite() 테스트 방법을 다음과 같이 덮어쓰십시오.

public static Test suite() {
  TestSuite suite= new TestSuite();
  suite.addTest(new IsoDateTest
("testToString"));
  return suite;
}

이 방법에서 TestSuite 객체는 인스턴스화가 가능하며 이 객체에 대한 여러 테스트가 추가적으로 이루어질 수 있습니다. 방법 단계에서 테스트를 정의하기 위해, 실시할 방법명을 매개변수로 채택하는 구성자를 이용 함으로써 테스트 클래스는 인스턴스화 됩니다. 이 구성자는 다음과 같이 실행됩니다.

public IsoDateTest(String name) {
  super(name);
}

위에 나와 있는 구성자와 메소드를 IsoDateTest 클래스에 추가하고 (또한 junit.framework.Test와 junit.framework.TestSuite를 임포트할 필요가 있을 수 있습니다), 터미널을 입력합니다.

selecting a test method

그림 3: 테스트 방법 선택

 
$ javac *.java
$ java junit.textui.TestRunner IsoDateTest
.
Time: 0,31
OK (1 test)

테스트 스위트에 추가된 유일한 테스트 메소드가 toString()인 점을 주목하십시오.
그래픽 인터페이스를 이용한 특정 테스트 메소드는 그림 3에 예시된 것처럼 테스트 Hierarchy 패널에서 선택함으로써 사용할 수 있습니다. 그러나 이 스위트는 전체 테스트 스위트가 한번 실시되면 √로 채워집니다.

TestSuite 객체에 한 테스트 사례의 모든 메소드를 추가하고자 하는 경우, 이를 위한 전용 생성자가 있습니다. 이 생성자는 테스트 사례의 class 객체를 인수로 채택합니다. 예를 들어, IsoDateTest 클래스를 이용하여 suite() 메소드를 다음과 같이 실행할 수 있습니다.

public static Test suite() {
  return new TestSuite(IsoDateTest.class);
}

한 제품을 출시하기 전에 그 제품에 대해 실시되는 모든 테스트와 같이 다른 테스트들로 구성된 일련의 테스트를 실시하고자 하는 경우에는, 원하는 테스트 스위트를 구성하는 suite() 방법을 구현할 클래스를 써야 합니다. 예를 들어 테스트 클래스 Atest 와 Btest를 쓰는 것에 대해 생각해 봅시다. 클래스 ATest의 모든 테스트를 포함하는 테스트 모음과 Btest라고 규정된 테스트 스위트를 정의하기 위해 다음과 같이 클래스를 쓸 수 있습니다.

import junit.framework.*;

/**
 * TestSuite that runs all tests.
 */
public class AllTests {

  public static Test suite() {
     TestSuite suite= new TestSuite("All Tests");
     suite.addTestSuite(ATest.class);
     suite.addTest(BTest.suite());
     return suite;
  }
}

마치 하나의 테스트 사례를 실시하는 것처럼 정확하게 이러한 테스트 스위트를 실시할 수 있습니다. 테스트가 한 테스트 스위트에 두 번 추가된다면 테스트 실행기는 그 테스트를 두 번 실행합니다. (테스트 스위트나 테스트 실행기 중 어떤 것도 테스트가 유일함을 확인하지 못합니다.) 실제 상황에서의 테스트 스위트 실행 방법을 이해하려면, JUnit 테스트 스위트 자체를 살펴봐야 합니다. 그러한 클래스의 소스는 JUnit 설치 프로그램의 junit/tests디렉토리에 있습니다.

test results

그림 4: 테스트 결과를 보여주는 보고서

테스트 실행기를 사용하지 않고 테스트를 하기 위해서는 테스트나 테스트 스위트에 main()을 추가하는 것이 때로는 편리합니다. 예를 들어, 표준 Java프로그램으로서 AllTests 스위트를 실행하기 위해서는 클래스에 다음과 같은 main()을 추가하십시오.

public static void main(String[] args) {
  junit.textui.TestRunner.run(suite());

}

이제 여러분은 java AllTests를 입력함으로써 이러한 테스트 조를 실행할 수 있습니다.
이 프레임워크는 또한 리소스를 픽스처(Fixture)에 모음으로써 코드를 절약하는 방법을 제공합니다. 예를 들어 예제 테스트 사례는 epoch와 eon이라는 두개의 기준 날짜를 사용합니다. 이러한 날짜를 각 테스트 방법에 다시 구축하는 것은 (잠재적으로 실수하기 쉬운 프로세스일 뿐만 아니라) 시간 낭비일 수 있습니다. 목록 2에서 예시된 것처럼, 이러한 테스트는 픽스처(Fixture)로 다시 쓸 수 있습니다.
참조 날짜를 테스트 클래스의 필드로 정의하고 이를 setUp()에 구축합니다. 이 메소드는 각 테스트 메소드 이전에 호출됩니다. 반대 방법은 tearDown() 인데, 이 방법은 각 테스트 방법이 실행된 후 리소스를 정리합니다. 이 실행에서는 쓰레기 수집기(garbage collector)가 정리하는 일을 하기 때문에 이 메소드는 사실상 아무것도 하지 않습니다. 이제 이러한 테스트 사례를 컴파일하고 실행하되, 이러한 테스트 사례의 소스 코드는 JUnit의 설치 디렉토리에 넣어야 한다는 점을 주의하십시오.

$ javac *.java
$ java junit.textui.TestRunner IsoDateTest2
.setUp()
testIsoDate()
tearDown()
.setUp()
testToString()

tearDown()

Time: 0,373

OK (2 tests)

어떠한 테스트 방법에서 날짜를 수정하더라도 다른 테스트에 역효과를 주지 않도록 참조 날짜를 설정해야 합니다. 이러한 두 가지 방법에 코드를 넣어 데이타베이스 접속과 같은 각 테스트에 필요한 리소스를 설치하고 정리하도록 합니다.

JUnit 배포는 반복적인 테스트 실행과 같은 새로운 기능을 제공하는 테스트 데코레이터인, 확장자(패키지에서는 junit.extensions)와 함께 제공됩니다. 또한 이 배포는 별도의 스레드에서 동시에 모든 테스트를 실행하고 모든 스레드가 끝날 때 멈추는 TestSuite와도 함께 제공됩니다.


出所 - http://www.oracle.com/global/kr/magazine/webcolumns/2003/o33junit_1.html

by artis