JUnit 4 로뛰어들기자바 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 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"); asserttrue("pattern did not validate zip code", isvalid); 새로운버전의이점자바 5 주석을사용할수있는 JUnit 4 에서는이러한규칙이모두사라졌다. 클래스계층구조는더이상필요하지않으며테스트로작동할메서드도새롭게정의된 @Test 주석으로만기술하면된다. Listing 2 는 Listing 1 에표시된것과동일한테스트를보여주지만주석을사용하여다시정의되어있다. Listing 2. 주석을사용한테스트
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"); 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 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 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"); 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 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"); 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"); 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"); 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 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"); assertequals("group(1) didn't equal -5051", "-5051", mtcher.group(1)); public void testzipcodegroupexception() throws Exception{ Matcher mtcher = this.pattern.matcher("22101-5051"); try{ mtcher.group(2); fail("no exception was thrown"); catch(indexoutofboundsexception e){ 해결방법이전버전의 JUnit 에서는 TestSetup 데코레이터를사용하여픽스쳐가한번만실행되도록지정할수있었지만 Listing 9 와같이이러한방식은번거로운작업이다 ( 필수 suite() 메서드참조 ): Listing 9. JUnit 4 이전의 TestSetup
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"); assertequals("group(1) didn't equal -5051", "-5051", mtcher.group(1)); public void testzipcodegroupexception() throws Exception { Matcher mtcher = pattern.matcher("22101-5051"); 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 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"); mtcher.group(2); 1 회용픽스쳐픽스쳐를한번만실행해야하는경우는어떻게해야할까? Listing 9 처럼이전스타일의데코레이터를구현하는대신 Listing 11 에서와같이 @BeforeClass 주석을사용할수있다. Listing 11. JUnit 4 에서 1 회용픽스쳐설정 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"); 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 테스트실행이클립스와같은 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 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"); 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 단계로이루어진다. 1. 매개변수를사용하지않는일반테스트를작성한다. 2. Collection 유형을반환하는 static 피더메서드를작성하고 @Parameter 주석으로표시한다. 3. 첫번째단계에서정의한일반메서드에필요한매개변수유형에대한클래스멤버를만든다. 4. 이러한매개변수유형을사용하고이를세번째단계에서정의한클래스멤버와연결하는 생성자를만든다. 5. @RunWith 주석을통해 Parameterized 클래스와함께실행할테스트케이스를지정한다. 이러한단계들을차례로살펴보자. 첫번째단계. 일반테스트작성 Listing 17 에서는정규식에대해여러값을확인하는일반테스트를보여준다. 여기에서는 phrase 와 match 값이정의되지않았다. Listing 17. 일반테스트 @Test public void verifygoodzipcode() throws Exception{ Matcher mtcher = this.pattern.matcher(phrase); 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 의본래기능을헤치는것이아니라아주손쉽게사용할수있다는사실이다. 따라서독자들도주석을시험해보기를바란다. 주석은테스트작성방식을한층더세련되게그리고편리하게만들어줄것이다. 기사의원문보기 Jump into JUnit 4: Streamlined testing with Java 5 annotations
참고자료 포럼에참여하기. "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 의회장이다. 효과적인개발자테스팅전략과통합기술력을바탕으로기업이소프트웨어품질을높일수있도록돕고있다.