시소당
자바 5 주석을 사용한 효율적인 테스트  |
|
 | 난이도 : 중급 Andrew Glover, President, Stelligent Incorporated
2007 년 4 월 10 일 JUnit
4에서는 자바(Java™) 5 주석(annotation)의 효율적인 유연성을 위해 기존의 엄격한 명명 규칙 및 상속 계층 구조를
없앴다. 테스트 전문가로 활동하고 있는 Andrew Glover는 본 튜토리얼에서 코드 품질과 관련하여 자신이 연재한 인기 있는
기술문서의 내용을 보충하는 시간을 마련하여 매개변수 테스트, 예외 테스트, 제한 시간 테스트 등 주석을 통해 새로운 기능을
활용하는 방법에 대해 설명한다. 또한 JUnit 4의 유연한 픽스쳐(fixture)에 대해 소개하고 스위트(suite) 대신
주석을 사용하여 테스트를 실행하기 전에 논리적으로 그룹화하는 방법에 대해서도 설명한다. 본 튜토리얼에는
이클립스(Eclipse)에서 실행되는 일부 샘플 테스트와 호환되지 않는 이전 버전의 앤트(Ant)에서 JUnit 4 테스트를
실행하기 위한 지침이 포함되어 있다.
시작하기 전에
튜토리얼 소개
자
바 5 주석은 JUnit에 커다란 변화를 가져왔으며 많은 테스트 프레임워크 개발자에게 효율적인 작업 방식으로 점차 인식되고
있지만 이들에게 익숙한 기술은 아니다. 본 튜토리얼에서는 JUnit 4의 가장 중요한 변경 사항에 대해 설명하고 독자가 이미
들어보았을지도 모르지만 아직 사용하고 있지는 않을 흥미로운 새 기능들에 대해 설명한다.
목적
본
튜토리얼에서는 JUnit 4의 기본 개념에 대해 단계별로 설명하고 특히 새로운 자바 5 주석 기능에 대해 자세히 다룬다. 한
시간 분량의 본 튜토리얼 학습을 마치면 JUnit 4의 주요 변경 사항에 대해 이해할 수 있을 뿐만 아니라 예외 테스트,
매개변수 테스트 및 유연한 새 픽스쳐 모델과 같은 기능에 대해 알게 된다. 또한 테스트를 선언하는 방법, 스위트 대신 주석을
사용하여 테스트를 실행하기 전에 논리적으로 그룹화하는 방법, 명령행뿐만 아니라 이클립스 3.2 또는 앤트에서 테스트를 실행하는
방법에 대해서도 설명한다.
필요한 사전 지식
본
튜토리얼을 최대한 활용하기 위해서는 일반적인 자바 개발에 익숙해야 한다. 본 튜토리얼에서는 또한 독자가 개발자 테스트의 중요성을
이해하고 있으며 기본 패턴 매칭에 익숙하다고 가정한다. JUnit 4 테스트 실행 섹션의 내용을 테스트하기 위해서는 이클립스
3.2를 IDE로 사용하고 앤트 1.6 이상을 사용해야 한다. 이전 버전의 JUnit에 익숙하지 않더라도 본 튜토리얼을 이해하는
데에는 문제가 없다.
시스템 요구 사항
본
튜토리얼의 코드를 시험해보려면 썬의 JDK 1.5.0_09 이상 또는 자바 기술 1.5.0 SR3용 IBM 개발자 킷이 설치된
시스템이 필요하다. 이클립스에서 JUnit 4 실행 섹션의 경우 이클립스 3.2 이상이 설치된 시스템이 필요하다. 앤트 섹션의
경우 버전 1.6 이상이 필요하다.
본 튜토리얼에서 권장하는 시스템 구성은 다음과 같다.
- 기본 메모리가 최소 500MB 이상이고 썬 JDK 1.5.0_09 이상 또는 자바 기술 1.5.0 SR3용 IBM 개발자 킷을 지원하는 시스템
- 소프트웨어 컴포넌트 및 예제를 설치하기 위한 최소 20MB 이상의 하드 디스크 여유 공간
본 튜토리얼의 지침은 마이크로소프트 윈도우 운영 체제를 기반으로 한다. 또한 본 튜토리얼에서 다루는 모든 도구는 리눅스와 유닉스 시스템에서도 작동한다. |
--다음페이지--
JUnit 4의 새로운 기능
자바 5 주석 덕분에 JUnit 4가 이전보다 더욱 가벼워졌고 유연해졌다. 일부 흥미로운 새 기능을 위해 이전의 엄격한 명명 규칙과 상속 계층 구조가 사라졌다. 다음은 JUnit 4의 새로운 기능을 간략히 설명해 놓은 목록이다.
- 매개변수 테스트
- 예외 테스트
- 제한 시간 테스트
- 유연한 픽스쳐
- 테스트를 쉽게 무시하는 방법
- 테스트를 논리적으로 그룹화하는 방법
이러한 기능과 더 많은 새로운 기능을 이후 섹션에서 설명하기에 앞서 JUnit 4의 가장 중요한 변경 사항에 대해 설명하겠다.
기존 버전의 문제
JUnit 4에 자바 5 주석 기능이 추가되기 전에 이 프레임워크에는 기능을 사용하는 데 반드시 필요한 두 가지 규칙이 존재했다. 첫 번째는 JUnit에서 논리적 테스트로 작동하도록 작성된 모든 메서드는 test라는 단어로 반드시 시작해야 한다는 것이다. testUserCreate
와
같이 이 단어로 시작하는 모든 메서드는 테스트 메서드 이전 및 이후에 픽스쳐 실행을 보장하는 잘 정의된 테스트 프로세스에 따라
실행되었다. 두 번째 규칙은 JUnit에서 테스트를 포함하는 클래스 객체를 인식하기 위해 클래스 자체가 JUnit의 TestCase
에서 확장되어야 한다는 점이다(또는 일부 파생). 이러한 두 가지 규칙을 위반하는 테스트는 실행할 수 없었다.
Listing 1은 JUnit 4 이전에 작성된 JUnit 테스트를 보여준다.
Listing 1. 이렇게 어렵게 작성해야 할 필요가 있을까? import java.util.regex.Matcher; import java.util.regex.Pattern;
import junit.framework.TestCase;
public class RegularExpressionTest extends TestCase { private String zipRegEx = "^\\d{5}([\\-]\\d{4})?$"; private Pattern pattern;
protected void setUp() throws Exception { this.pattern = Pattern.compile(this.zipRegEx); }
public void testZipCode() throws Exception{ Matcher mtcher = this.pattern.matcher("22101"); boolean isValid = mtcher.matches(); assertTrue("Pattern did not validate zip code", isValid); } }
|
 | 많은 사람들이 JUnit 4에서 주석이 사용된 것은 .NET의 NUnit과 TestNG의 영향을 받은 것이라고 말한다. 다른 테스트 프레임워크에서의 주석에 대한 자세한 내용은 참고자료를 참조하기 바란다. |
|
새로운 버전의 이점
자바 5 주석을 사용할 수 있는 JUnit 4에서는 이러한 규칙이 모두 사라졌다. 클래스 계층 구조는 더 이상 필요하지 않으며 테스트로 작동할 메서드도 새롭게 정의된 @Test
주석으로만 기술하면 된다.
Listing 2는 Listing 1에 표시된 것과 동일한 테스트를 보여주지만 주석을 사용하여 다시 정의되어 있다.
Listing 2. 주석을 사용한 테스트 import java.util.regex.Matcher; import java.util.regex.Pattern;
import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.assertTrue;
public class RegularExpressionTest { private static String zipRegEx = "^\\d{5}([\\-]\\d{4})?$"; private static Pattern pattern;
@BeforeClass public static void setUpBeforeClass() throws Exception { pattern = Pattern.compile(zipRegEx); }
@Test public void verifyGoodZipCode() throws Exception{ Matcher mtcher = this.pattern.matcher("22101"); boolean isValid = mtcher.matches(); assertTrue("Pattern did not validate zip code", isValid); } }
|
Listing 2에서 언급한 테스트는 코드로 작성하기가 더 쉽지 않을 수도 있지만 확실한 것은 더 쉽게 이해할 수 있다는 것이다.
간단한 문서화
주석이 갖는 한 가지 유용한 점은 프레임워크의 내부 모델에 대한 자세한 이해 없이도 각 메서드의 사용 의도를 명확하게 문서화한다는 점이다. @Test
로
테스트 메서드를 표시하는 것 이상으로 더 명확한 방법이 있을까? 이는 각 메서드가 전반적인 테스트 케이스에서 어떤 역할을
담당하는지만 이해하고 싶어도 JUnit 규칙에 대한 상당한 이해가 필요했던 기존 JUnit 스타일에 비해 크게 향상된 점이다.
이미 작성된 테스트를 파싱할 때에도 주석은 큰 도움이 되지만 테스트 작성 중에 추가 작업이 발생할 경우에는 더욱 필수적인 요소가 된다.
--다음페이지--
주석을 사용한 테스트
자바 5
주석으로 인해 JUnit 4는 이전 버전과는 크게 다른 프레임워크가 되었다. 이 섹션에서는 테스트 선언과 예외 테스트 같은 핵심
영역뿐만 아니라 제한 시간 테스트 영역에서 주석을 사용하는 방법과 원치 않는 또는 사용할 수 없는 테스트를 무시하는 방법에 대해
설명한다.
테스트 선언
JUnit 4에서 테스트를 선언하기 위해서는 단순히 테스트 메서드에 @Test
주석만 덧붙이면 된다. Listing 3에서와 같이 특정 클래스에서 확장할 필요가 없다.
Listing 3. JUnit 4에서 테스트 선언 import java.util.regex.Matcher; import java.util.regex.Pattern;
import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.assertFalse;
public class RegularExpressionTest { private static String zipRegEx = "^\\d{5}([\\-]\\d{4})?$"; private static Pattern pattern; @BeforeClass public static void setUpBeforeClass() throws Exception { pattern = Pattern.compile(zipRegEx); }
@Test public void verifyZipCodeNoMatch() throws Exception{ Matcher mtcher = this.pattern.matcher("2211"); boolean notValid = mtcher.matches(); assertFalse("Pattern did validate zip code", notValid); } }
|
정적 가져오기에 대해 알아야 할 것
필자는 Listing 3에서 Assert
클래스의 assertFalse()
메서드를 가져오기 위해 자바 5의 정적 가져오기 기능을 사용했다. 이는 테스트 클래스가 이전 버전의 JUnit에서와 같이 TestCase
에서 확장되지 않기 때문이다.
예외 테스트
이전 버전의 JUnit에서와 같이 일반적으로 테스트가 Exception
을 throw하는 경우를 지정하는 것이 좋다. 이 규칙을 무시해야 하는 유일한 경우는 특정 예외에 대한 테스트를 시도하려는 경우다. 테스트가 예외를 throw하면 프레임워크가 실패를 보고한다.
특정 예외에 대한 테스트를 수행하고자 할 때 JUnit 4의
@Test
주석은 테스트가 예외시 throw해야 하는 예외 유형을 나타내는 expected
매개변수를 지원한다.
간단한 비교로 새로운 매개변수의 차이점을 이해할 수 있다.
JUnit 3.8에서 예외 테스트
testZipCodeGroupException()
으로 명명된 Listing 4의 JUnit 3.8 테스트는 선언한 정규식의 세 번째 그룹을 얻으려고 시도할 경우
IndexOutOfBoundsException
이 발생하는 것으로 확인된다.
Listing 4. JUnit 3.8에서 예외 테스트 import java.util.regex.Matcher; import java.util.regex.Pattern;
import junit.framework.TestCase;
public class RegularExpressionTest extends TestCase {
private String zipRegEx = "^\\d{5}([\\-]\\d{4})?$"; private Pattern pattern;
protected void setUp() throws Exception { this.pattern = Pattern.compile(this.zipRegEx); }
public void testZipCodeGroupException() throws Exception{ Matcher mtcher = this.pattern.matcher("22101-5051"); boolean isValid = mtcher.matches(); try{ mtcher.group(2); fail("No exception was thrown"); }catch(IndexOutOfBoundsException e){ } } }
|
이전 버전의 JUnit에서는 try
/catch
를 작성하여 예외가 발생하지 않으면 실패하는 이러한 간단한 테스트를 위해서도 상당히 많은 양의 코드를 작성해야 한다.
JUnit 4에서 예외 테스트
Listing 5의 예외 테스트는 Listing 4와 동일하지만 새로운 expected
매개변수를 사용한다는 점이 다르다(Listing 4에서 @Test
주석에 IndexOutOfBoundsException
예외를 전달하여 테스트를 수정할 수 있었다).
Listing 5. 'expected' 매개변수를 사용한 예외 테스트 import java.util.regex.Matcher; import java.util.regex.Pattern;
import org.junit.BeforeClass; import org.junit.Test;
public class RegularExpressionJUnit4Test { private static String zipRegEx = "^\\d{5}([\\-]\\d{4})?$"; private static Pattern pattern;
@BeforeClass public static void setUpBeforeClass() throws Exception { pattern = Pattern.compile(zipRegEx); }
@Test(expected=IndexOutOfBoundsException.class) public void verifyZipCodeGroupException() throws Exception{ Matcher mtcher = this.pattern.matcher("22101-5051"); boolean isValid = mtcher.matches(); mtcher.group(2); } }
|
제한 시간 테스트
JUnit 4의 테스트 케이스에서는 제한 시간 값을 매개변수로 사용할 수 있다. Listing 6에서와 같이 timeout
값은 테스트가 실행하는 데 걸리는 최대 시간을 나타낸다. 시간이 초과되면 테스트가 실패한다.
Listing 6. timeout 값을 사용한 테스트 @Test(timeout=1) public void verifyFastZipCodeMatch() throws Exception{ Pattern pattern = Pattern.compile("^\\d{5}([\\-]\\d{4})?$"); Matcher mtcher = pattern.matcher("22011"); boolean isValid = mtcher.matches(); assertTrue("Pattern did not validate zip code", isValid); }
|
제한 시간을 사용한 테스트는 쉽게 작성할 수 있다. 메서드에서 단순히 @Test
다음에 timeout
값을 삽입하면 자동화된 제한 시간 테스트를 구현할 수 있다.
테스트 무시
JUnit
4 버전이 나오기 전에는 깨져 있거나 불완전한 테스트를 무시하기가 어려웠다. 프레임워크에서 특정 테스트를 무시하도록 하려면
테스트 명칭을 따르지 않도록 테스트 이름을 바꾸어야 했다. 예를 들어 필자도 테스트가 실행되지 않도록 표시하기 위해 테스트
메서드 앞에 "_"를 붙이는 습관이 있었다.
JUnit 4에서는 @Ignore
를 붙인 주석을 통해 프레임워크에서 특정 테스트 메서드를 무시하도록 할 수 있다. 또한 다른 개발자가 무시된 테스트를 이상하게 여기지 않도록 메시지 설명을 전달할 수도 있다.
@Ignore 주석
Listing 7은 정규식이 아직 작동하지 않는 테스트를 쉽게 무시하는 방법을 보여준다.
Listing 7. 테스트 무시 @Ignore("this regular expression isn't working yet") @Test public void verifyZipCodeMatch() throws Exception{ Pattern pattern = Pattern.compile("^\\d{5}([\\-]\\d{4})"); Matcher mtcher = pattern.matcher("22011"); boolean isValid = mtcher.matches(); assertTrue("Pattern did not validate zip code", isValid); }
|
테스트 무시에 대한 알림 메시지
예를 들어 이클립스에서 이 테스트를 실행하면 그림 1과 같이 테스트가 무시되었다는 알림이 보고된다.
Figure 1. 그림 1. 무시된 테스트가 이클립스에서 표시되는 모습
--다음페이지--
테스트 픽스쳐
테스트 픽스쳐는
JUnit 4의 새로운 기능은 아니지만 픽스쳐 모델이 새롭게 향상되었다. 이 섹션에서는 픽스쳐를 사용하는 이유와 경우에 대해
설명하고 이전 버전의 유연하지 않은 픽스쳐와 JUnit 4의 새로운 모델 간의 차이점을 설명한다.
픽스쳐를 사용하는 이유
픽
스쳐는 특정 로직이 테스트 전후에 실행되도록 보장하는 하나의 약정이므로 손쉽게 재활용할 수 있다. 이전 버전의 JUnit에서 이
약정은 픽스쳐를 구현했는지 여부에 관계없이 적용되었다. 하지만 JUnit 4에서 픽스쳐는 주석을 통해 명시적으로 변경되므로
사용자가 픽스쳐를 사용하도록 결정한 경우에만 약정이 적용된다.
테스트 전후에 픽스쳐를 실행할 수 있도록 보장하는 약정을 통해 재사용이 가능한
로직을 코딩할 수 있다. 예를 들어 이러한 로직은 여러 테스트 케이스 또는 로직에서 데이터 종속 테스트를 실행하기 전에
데이터베이스를 채우도록 테스트를 수행할 클래스를 초기화할 수 있다. 이러한 테스트 케이스는 공통 로직을 사용하므로 어느 쪽이든
픽스쳐를 사용하면 테스트 케이스를 더 쉽게 관리할 수 있다.
픽스쳐는 동일한 로직을
사용하는 여러 테스트를 실행 중이고 이들 중 일부 또는 전체가 실패할 경우에 특히 유용하다. 각 테스트의 설정 로직에서 실패
원인을 확인하는 대신 한 곳에서 실패 원인을 추론할 수 있다. 또한 테스트가 일부만 성공하고 일부는 실패하는 경우 이러한 실패의
공통 원인으로 픽스쳐 로직을 검사하지 않아도 된다.
유연하지 않은 픽스쳐
O이전 버전의 JUnit에서는 다소 유연하지 않은 픽스쳐 모델이 사용되었다. 여기에서는 setUp()
과 tearDown()
메서드를 사용하여 모든 테스트 메서드를 래핑해야 했다. Listing 8에서는 이러한 모델의 잠재적인 단점을 확인할 수 있다. 여기에서는 setUp()
메서드가 구현되며 따라서 정의된 각 테스트에 대해 한 번씩 두 번 실행된다.
Listing 8. 유연하지 않은 픽스쳐 import java.util.regex.Matcher; import java.util.regex.Pattern;
import junit.framework.TestCase;
public class RegularExpressionTest extends TestCase {
private String zipRegEx = "^\\d{5}([\\-]\\d{4})?$"; private Pattern pattern;
protected void setUp() throws Exception { this.pattern = Pattern.compile(this.zipRegEx); }
public void testZipCodeGroup() throws Exception{ Matcher mtcher = this.pattern.matcher("22101-5051"); boolean isValid = mtcher.matches(); assertEquals("group(1) didn't equal -5051", "-5051", mtcher.group(1)); }
public void testZipCodeGroupException() throws Exception{ Matcher mtcher = this.pattern.matcher("22101-5051"); boolean isValid = mtcher.matches(); try{ mtcher.group(2); fail("No exception was thrown"); }catch(IndexOutOfBoundsException e){ } } }
|
해결 방법
이전 버전의 JUnit에서는 TestSetup
데코레이터를 사용하여 픽스쳐가 한 번만 실행되도록 지정할 수 있었지만 Listing 9와 같이 이러한 방식은 번거로운 작업이다(필수 suite()
메서드 참조):
Listing 9. JUnit 4 이전의 TestSetup import java.util.regex.Matcher; import java.util.regex.Pattern;
import junit.extensions.TestSetup; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import junit.textui.TestRunner;
public class OneTimeRegularExpressionTest extends TestCase {
private static String zipRegEx = "^\\d{5}([\\-]\\d{4})?$"; private static Pattern pattern;
public static Test suite() { TestSetup setup = new TestSetup( new TestSuite(OneTimeRegularExpressionTest.class)) { protected void setUp() throws Exception { pattern = Pattern.compile(zipRegEx); } }; return setup; }
public void testZipCodeGroup() throws Exception { Matcher mtcher = pattern.matcher("22101-5051"); boolean isValid = mtcher.matches(); assertEquals("group(1) didn't equal -5051", "-5051", mtcher.group(1)); }
public void testZipCodeGroupException() throws Exception { Matcher mtcher = pattern.matcher("22101-5051"); boolean isValid = mtcher.matches(); try { mtcher.group(2); fail("No exception was thrown"); } catch (IndexOutOfBoundsException e) { } } }
|
JUnit 4 이전에는 픽스쳐의 이점을 얻기 위해서는 감수해야 하는 희생이 더 많았다.
4.0에서의 유연성
JUnit
4에서는 주석을 사용하여 픽스쳐에 따른 상당한 오버헤드를 없앰으로써 모든 테스트에 대해 또는 전체 클래스에 대해 한 번 픽스쳐를
실행하거나 아예 실행하지 않을 수도 있다. 픽스쳐 주석은 클래스 수준의 픽스쳐 2개와 메서드 수준의 픽스쳐 2개가 존재한다.
클래스 수준의 경우
@BeforeClass
와 @AfterClass
가 있으며 메서드(또는 테스트) 수준의 경우
@Before
와 @After
가 있다.
Listing 10의 테스트 케이스에는 @Before
주석을 사용하여 두 테스트에 대해 실행되는 픽스쳐가 들어 있다.
Listing 10. 주석을 사용한 유연한 픽스쳐 import java.util.regex.Matcher; import java.util.regex.Pattern;
import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertFalse;
public class RegularExpressionJUnit4Test { private static String zipRegEx = "^\\d{5}([\\-]\\d{4})?$"; private static Pattern pattern;
@Before public static void setUpBeforeClass() throws Exception { pattern = Pattern.compile(zipRegEx); }
@Test public void verifyZipCodeNoMatch() throws Exception{ Matcher mtcher = this.pattern.matcher("2211"); boolean notValid = mtcher.matches(); assertFalse("Pattern did validate zip code", notValid); }
@Test(expected=IndexOutOfBoundsException.class) public void verifyZipCodeGroupException() throws Exception{ Matcher mtcher = this.pattern.matcher("22101-5051"); boolean isValid = mtcher.matches(); mtcher.group(2); } }
|
1회용 픽스쳐
픽스쳐를 한 번만 실행해야 하는 경우는 어떻게 해야 할까? Listing 9처럼 이전 스타일의 데코레이터를 구현하는 대신 Listing 11에서와 같이 @BeforeClass
주석을 사용할 수 있다.
Listing 11. JUnit 4에서 1회용 픽스쳐 설정 import java.util.regex.Matcher; import java.util.regex.Pattern;
import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertFalse;
public class RegularExpressionJUnit4Test { private static String zipRegEx = "^\\d{5}([\\-]\\d{4})?$"; private static Pattern pattern;
@BeforeClass public static void setUpBeforeClass() throws Exception { pattern = Pattern.compile(zipRegEx); }
@Test public void verifyZipCodeNoMatch() throws Exception{ Matcher mtcher = this.pattern.matcher("2211"); boolean notValid = mtcher.matches(); assertFalse("Pattern did validate zip code", notValid); }
@Test(expected=IndexOutOfBoundsException.class) public void verifyZipCodeGroupException() throws Exception{ Matcher mtcher = this.pattern.matcher("22101-5051"); boolean isValid = mtcher.matches(); mtcher.group(2); } }
|
tearDown() 메서드
이전의 tearDown()
기능은 새로운 픽스쳐 모델에서 사라지지 않았다. tearDown()
메서드를 실행하려면 새 메서드를 생성하고 필요에 따라 @After
또는 @AfterClass
를 사용하면 된다.
향상된 사용성
JUnit 4에서는 테스트 케이스에서 두 개 이상의 픽스쳐를 지정할 수 있다. 새로운 주석 중심의 픽스쳐에서는 여러 @BeforeClass
픽스쳐 메서드를 만드는 데 어떠한 제한도 없다. 하지만 현재 버전의 JUnit 4에서는 어떤 픽스쳐 메서드를 먼저 실행하도록 지정할 수 없으므로 두 개 이상의 픽스쳐를 사용할 때 이 점에 주의해야 한다.
--다음페이지--
테스트 실행: JUnit 4에서 테스트
새
롭게 향상된 JUnit 4에서 가장 놀라운 특징 중 하나는 테스트를 논리적으로 그룹화하고 이를 단일 유닛으로 실행하는 데
사용되는 메커니즘인 스위트가 없다는 점이다. 이 섹션에서는 스위트를 대신하는 효율적인 새로운 주석 기능에 대해 소개하고 이클립스
및 앤트에서 JUnit 4 테스트를 실행하는 방법을 보여준다.
이전 방식의 스위트
차이점을 비교해 볼 수 있도록 Listing 12에 표시된 이전 방식의 JUnit 스위트를 확인해 보자(이 스위트는 두 개의 논리적 테스트 클래스를 그룹화하고 이를 단일 유닛으로 실행한다).
Listing 12. 이전 방식의 JUnit 스위트 import junit.framework.Test; import junit.framework.TestSuite;
public class JUnit3Suite {
public static Test suite() { TestSuite suite = new TestSuite(); suite.addTest(OneTimeRegularExpressionTest.suite()); suite.addTestSuite(RegularExpressionTest.class); return suite; } }
|
두 가지 뛰어난 새로운 주석 기능
JUnit 4에서 스위트는 새로운 주석 두 가지로 대체되었다. 첫 번째로 @RunWith
는 프레임워크에 내장된 러너(runner)가 아닌 다른 러너를 통해 특정 테스트 클래스를 손쉽게 실행할 수 있게 해준다. JUnit 4는 @RunWith
주석에서 지정해야 하는 Suite
라는 이름의 스위트 러너를 번들로 포함한다. 또한 테스트 스위트를 나타내기 위한 클래스 목록을 매개변수로 취하는 @SuiteClasses
라는 주석을 제공해야 한다.
Listing 13. 편리한 기능의 주석 import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class) @SuiteClasses({ParametricRegularExpressionTest.class, RegularExpressionTest.class, TimedRegularExpressionTest.class}) public class JUnit4Suite {
}
|
 |
새로운 JUnit 4 주석이 어렵게 보이더라도 걱정할 필요는 없다. JUnit 4를 고안한 개발자들도 비슷한 생각을 갖고 있다.
Javadocs에서 JUnit 개발자들은 "사람들이 실제로 사용하는 방식대로" 러너 API가 변화될 것으로 예상한다고 설명한다.
최소한 이들은 이점에서 솔직하다.
|
|
이클립스에서 JUnit 4 테스트 실행
이클립스와 같은 IDE에서 또는 명령행을 통해 JUnit 4 테스트 클래스를 실행할 수 있다. Run As JUnit 테스트 옵션을 선택하면 이클립스 버전 3.2 이상에서 JUnit 테스트를 실행할 수 있다. 명령행을 통해 테스트를 실행하려면 org.junit.runner.JUnitCore
클래스를 실행하고 테스트의 정규화된 이름을 인수로 전달해야 한다.
예를 들어 이클립스에서 번들로 제공되는 JUnit 러너를 사용하지 않고자 하는 경우 새로운 실행 구성을 정의하고 그림 2와 같이 JUnitCore
클래스를 지정하면 된다.
그림 2. 이클립스에서 JUnit 4 명령행 테스트를 실행하기 위한 첫 번째 단계
테스트 지정
그런 다음 그림 3과 같이 Arguments 탭의 "Program arguments" 텍스트 상자에 테스트의 정규화된 이름을 추가하여 실행할 테스트를 지정해야 한다.
그림 3. 이클립스에서 JUnit 명령행 테스트를 실행하기 위한 두 번째 단계
앤트와 JUnit 4
앤
트와 JUnit은 지금까지 훌륭한 팀으로 존재해 왔으며 많은 개발자들은 이러한 관계로 인해 뛰어난 JUnit 4를 얻게 될
것이라고 기대했다. 그리고 결과적으로도 이러한 사실은 실제로 나타났다. 1.7 이전의 앤트 버전을 실행 중인 경우 JUnit 4
테스트를 즉시 실행할 수 없다. 하지만 그렇더라도 테스트 자체를 실행할 수 없는 것은 아니며 단지 즉시 실행할 수 없을 뿐이다.
잘못 맞춰진 짝
1.7 이전의 앤트 버전에서 JUnit 4 테스트(Listing 14 참조)를 실행하면 흥미로운 결과가 발생한다.
Listing 14. 간단한 JUnit 4 테스트 클래스 import java.util.regex.Matcher; import java.util.regex.Pattern;
import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.assertTrue;
public class RegularExpressionTest { private static String zipRegEx = "^\\d{5}([\\-]\\d{4})?$"; private static Pattern pattern;
@BeforeClass public static void setUpBeforeClass() throws Exception { pattern = Pattern.compile(zipRegEx); }
@Test public void verifyGoodZipCode() throws Exception{ Matcher mtcher = this.pattern.matcher("22101"); boolean isValid = mtcher.matches(); assertTrue("Pattern did not validate zip code", isValid); } }
|
여러 번의 테스트 실패
앤트에서 오래된 junit
작업을 사용하면 Listing 15와 같은 에러가 발생한다.
Listing 15. 여러 가지 에러 [junit] Running test.com.acme.RegularExpressionTest [junit] Tests run: 1, Failures: 1, Errors: 0, Time elapsed: 0.047 sec [junit] Testsuite: test.com.acme.RegularExpressionTest [junit] Tests run: 1, Failures: 1, Errors: 0, Time elapsed: 0.047 sec
[junit] Testcase: warning took 0.016 sec [junit] FAILED [junit] No tests found in test.com.acme.RegularExpressionTest [junit] junit.framework.AssertionFailedError: No tests found in test.com.acme.RegularExpressionTest [junit] Test test.com.acme.RegularExpressionTest FAILED
|
해결 방법
1.7 이전의 앤트 버전에서 JUnit 4 테스트를 실행하려면 Listing 16에서와 같이 JUnit4TestAdapter
의 인스턴스를 반환하는 suite()
메서드를 사용하여 테스트 케이스를 수정해야 한다.
Listing 16. 이전 메서드를 새롭게 활용 public static junit.framework.Test suite(){ return new JUnit4TestAdapter(RegularExpressionTest.class); }
|
이 인스턴스에서는 이름이 비슷한 @Test
주석으로 인해 Test
의 반환 유형을 정규화해야 한다. 하지만 suite()
메서드를 배치하면 어떤 버전의 앤트에서도 JUnit 4 테스트가 문제없이 실행된다.
--다음페이지--
매개변수 테스트
애플리케이션의
비즈니스 로직에서는 테스트가 제대로 될 때까지 사용자가 매우 많은 테스트를 작성해야 한다. 이전 버전의 JUnit에서 이러한
유형의 시나리오는 테스트에서 메서드에 대한 매개변수 그룹이 다양하면 각 고유 그룹에 대한 테스트 케이스를 작성해야 했기 때문에
매우 많은 불편을 초래했다.
JUnit 4에서는 매개변수 값을 지정할 수 있는 일반 테스트를 작성할 수 있게 해주는 새로운 기능이 도입되었다. 결과적으로 테스트 케이스 하나를 작성하여 이전에 작성한 각 매개변수에 대해 이를 여러 번 실행할 수 있다.
간단한 매개변수를 통한 테스트
JUnit 4에서 매개변수 테스트를 작성하는 데에는 다음과 같이 간단한 5단계로 이루어진다.
- 매개변수를 사용하지 않는 일반 테스트를 작성한다.
-
Collection
유형을 반환하는 static
피더 메서드를 작성하고 @Parameter
주석으로 표시한다. - 첫 번째 단계에서 정의한 일반 메서드에 필요한 매개변수 유형에 대한 클래스 멤버를 만든다.
- 이러한 매개변수 유형을 사용하고 이를 세 번째 단계에서 정의한 클래스 멤버와 연결하는 생성자를 만든다.
-
@RunWith
주석을 통해 Parameterized
클래스와 함께 실행할 테스트 케이스를 지정한다.
이러한 단계들을 차례로 살펴보자.
첫 번째 단계. 일반 테스트 작성
Listing 17에서는 정규식에 대해 여러 값을 확인하는 일반 테스트를 보여준다. 여기에서는 phrase
와 match
값이 정의되지 않았다.
Listing 17. 일반 테스트 @Test public void verifyGoodZipCode() throws Exception{ Matcher mtcher = this.pattern.matcher(phrase); boolean isValid = mtcher.matches(); assertEquals("Pattern did not validate zip code", isValid, match); }
|
두 번째 단계. 피더 메서드 작성
다음 단계는 피더 메서드를 작성하는 것이다. 이는 static
으로 선언해야 하고 Collection
유형을 반환해야 한다. 이 메서드는
@Parameters
a주석으로 표시해야 한다. Listing 18에서와 같이 메서드 내에서 다차원 Object
배열을 만들고 이를 List
로 변환한다.
Listing 18. @Parameters 주석으로 표시된 피더 메서드 @Parameters public static Collection regExValues() { return Arrays.asList(new Object[][] { {"22101", true }, {"221x1", false }, {"22101-5150", true }, {"221015150", false }}); }
|
세 번째 단계. 클래스 멤버 두 개 작성
매개변수 유형은 String
과 boolean
이기 때문에 다음과 같이 클래스 멤버를 두 개 작성해야 한다.
Listing 19. 클래스 멤버 두 개 선언 private String phrase; private boolean match;
|
네 번째 단계. 생성자 작성
다음으로 작성하는 생성자는 Listing 20에서와 같이 클래스 멤버를 매개변수 값에 연결한다.
Listing 20. 값을 비교하는 생성자 public ParametricRegularExpressionTest(String phrase, boolean match) { this.phrase = phrase; this.match = match; }
|
다섯 번째 단계. Parameterized 클래스 지정
마지막으로 클래스 수준에서 이 테스트가 Listing 21에서와 같이 Parameterized
클래스로 실행되도록 지정한다.
Listing 21. Parameterized와 @RunWith 주석 지정 @RunWith(Parameterized.class) public class ParametricRegularExpressionTest { //... }
|
테스트 실행
테스트 클래스를 실행하면 일반 verifyGoodZipCode()
테스트 메서드가 Listing 18의 regExValues()
데이터 피더 메서드에 정의된 각 값 쌍에 대해 한 번씩 네 번 실행된다.
예를 들어 이 테스트를 이클립스에서 실행하면 그림 4에서와 같이 테스트 실행을 4회 했음을 보고한다.
그림 4. 이클립스에서 실행된 매개변수 테스트
--다음페이지--
기타 새로운 기능
지금까지 설명한 중요한 변경 사항 외에도 JUnit 4에는 몇 가지 추가된 기능과 없어진 기능이 있다. 그 예로 assert 메서드가 새로 추가되었고 터미널 상태가 없어졌다.
새로운 assert
JUnit 4에는 배열 내용을 비교하기 위한 새로운 assert 메서드가 추가되었다. 그렇게 중요한 기능은 아니지만, 덕분에 사용자는 더 이상 배열의 내용을 반복적으로 검사하여 각 개별 항목을 확인할 필요가 없어졌다.
예를 들어 Listing 22에 보이는 코드는 이전 버전의 JUnit에서는 사용할 수 없다. 이 테스트 케이스는 각 배열의 두 번째 요소가 조금 다르기 때문에 실패한다.
Listing 22. JUnit 4에서 배열을 지원하는 assertEquals @Test public void verifyArrayContents() throws Exception{ String[] actual = new String[] {"JUnit 3.8.x", "JUnit 4", "TestNG"}; String[] var = new String[] {"JUnit 3.8.x", "JUnit 4.1", "TestNG 5.5"}; assertEquals("the two arrays should not be equal", actual, var); }
|
에러 표시 안 함
JUnit 4에서 사소할 수도 있지만 중요한 변경 사항 중 하나는 에러 표기가 사라졌다는 점이다. 이전 버전에서는 실패 개수와 에러 개수가 모두 보고되었지만 JUnit 4에서는 테스트가 성공하거나 실패하는 것만 표시된다.
흥미롭게도 하나의 상태가 제거되었지만 이번에는 테스트를 무시하는 기능으로 인해 새로운 상태가 추가되었다. 일련의 테스트를 실행하면 JUnit 4에서는 실행된 테스트 개수와 실패 개수, 무시된 테스트 개수를 보고한다.
결론
JUnit 4가 원래의
설계 의도와 많이 달라졌지만 그렇다고 해서 이 프레임워크가 완전히 다른 방식으로 작동하는 것은 아니다. 원래의 프레임워크가 갖고
있는 성능과 단순성은 그대로 유지된다. 실제로 프레임워크를 자세히 살펴보면 일부 뛰어난 새 기능들이 추가되었지만 개발자의 테스트
기술을 혁신적으로 이끌었던 중요한 원칙은 그대로 남아 있다는 것을 알 수 있다.
본
튜토리얼에서는 테스트 선언에서 매개변수 테스트까지 JUnit 4의 전반적인 기능을 확인하기 위한 단계들을 살펴보았다. 제한 시간
테스트 및 예외 테스트와 같은 새로운 기능을 확인하고 픽스쳐 및 논리적인 그룹화와 같은 익숙한 기능의 변경 사항도 확인했다.
또한 이클립스에서 수행되는 테스트 방식을 살펴보고 1.7 이전 버전을 포함하여 모든 앤트 버전에서 테스트를 실행할 수 있게
해주는 간단한 해결 방법도 배웠다.
본 튜토리얼에서 독자가 반드시 알았으면 하는
내용이 있다면 주석이 JUnit의 본래 기능을 헤치는 것이 아니라 아주 손쉽게 사용할 수 있다는 사실이다. 따라서 독자들도
주석을 시험해 보기를 바란다. 주석은 테스트 작성 방식을 한층 더 세련되게 그리고 편리하게 만들어 줄 것이다.
기사의 원문보기
참고자료
- 포럼에 참여하기.
- "An early look at JUnit 4" (Elliotte Rusty Harold, developerWorks, 2005년 9월): 엘리옷 해롤드가 JUnit 4의 새로운 기능에 대해 설명한다.
- "In pursuit of code quality: JUnit 4 vs. TestNG" (Andrew Glover, developerWorks, 2006년 8월): JUnit 4가 TestNG의 뛰어난 모든 기능을 도입했다는 것이 사실일까?
- "테스트엔지로 자바 단위 테스트를 쉽게! (한글)" (Filippo Diotalevi, 한국 developerWorks, 2005년 1월): TestNG는 강력하고, 혁신적이며, 확장 가능하고 유연할 뿐만 아니라 자바 주석을 사용한 흥미로운 애플리케이션 구축 방식을 제시한다.
- "Annotations in Tiger, Part 1: 메타데이터를 자바 코드에 추가하기 (한글)"
(Brett McLaughlin, 한국 developerWorks, 2004년 9월): Brett McLaughlin이
메타데이터가 유용한 이유에 대해 설명하고 자바 언어의 주석 기능을 소개하고 자바 5에 내장된 주석에 대해 자세히 설명한다.
- "Classworking toolkit: Annotations vs. configuration files" (Dennis Sosnoski, developerWorks, 2005년 8월): Dennis Sosnoski가 구성 파일이 여전히 활용되는 이유에 대해 설명한다.
- "JUnit Reloaded" (Ralf Stuckert, Java.net, 2006년 12월): 이전 버전과 JUnit 4를 비교한 내용을 소개한다.
- "JUnit 4 you" (Fabiano Cruz, Fabiano Cruz's Blog, 2006년 6월): JUnit 4에 대한 IDE 지원 및 도구를 간략히 소개한다.
- "Limiting asserts in test cases" (thediscoblog.com): JUnit, TestNG의 베스트 프랙티스를 살펴본다.
- "DbUnit with JUnit 4" (testearly.com): JUnit 4가 이전 버전과 다르다고 해서 이전 버전의 JUnit을 위해 작성된 확장 프레임워크에 JUnit 4를 사용할 수 없는 것은 아니다.
- "Using JUnit extensions in TestNG" (Andrew Glover, thediscoblog.com, 2006년 3월): 프레임워크가 JUnit 확장이어야 한다고 해서 TestNG에서 사용할 수 없는 것은 아니다.
-
In pursuit of code quality 연재 (Andrew Glover, developerWorks): 코드 메트릭에서 테스트 프레임워크 및 리팩토링까지 여기에 연재된 모든 기술문서를 참조하기 바란다.
필자소개
 |

|  | Andrew Glover는 Stelligent Incorporated의 회장이다. 효과적인 개발자 테스팅 전략과 통합 기술력을 바탕으로 기업이 소프트웨어 품질을 높일 수 있도록 돕고 있다. |