'JAVA/Unit Test'에 해당되는 글 6건

  1. 2009.02.16 Junit -03-
  2. 2009.02.16 Junit -02-
  3. 2009.02.16 Junit -01-
  4. 2009.02.16 단위 테스트
  5. 2008.09.19 EasyMock 2.3문서
  6. 2008.09.11 JUnit
JAVA/Unit Test2009. 2. 16. 11:38

JUnit4.X 이후 변화

비교대상 JUnit3.8이전 JUnit4.0이후
Jdk5.0 Pradigm 도입 - Annotation 사용
Assertion Method를 static import 사용
Test Class junit.framework.TestCase상속 TestCase 상속받지 않음
Set up, teat Down setUp(), tearDown() @Before, @After
(다수의 메서드 지정가능)
Test Method testXXX()로 정의 @Test
@Test(expected=Error.class)
Junit4.4이후 - assertThat(value,matcher statement);
assumeThat()추가

TestCase에 Main() 메서드를 추가하기.

테스트를 실행시킬때 여러가지 방법이 있습니다만 전체 클래스에 메인메서드를 추가해서 사용해도 편리하겠죠.
메인메서드는 org.junit.runner.JUnutCore#main()을 호출합니다.

package testJunit;

import static org.junit.Assert.*;

import org.junit.*;
import org.junit.runner.JUnitCore;

public class SampleTest01 {

    @Test
    public void getStringOne() {
    	String str ="JUnit4.5";
    	assertEquals(str.charAt(0),'b');
    }
    public static void main(String[] args){
	JUnitCore.main(SampleTest01.class.getName());
    }
}

실행결과


콘솔창에서 실행한 결과입니다.

assertEquals(str.charAt(0),'J');

올바른 내용이 나오도록 수정하고 확인하면


올바른 테스트를 축하 드립니다. 위의 내용을 Run As > Java Application 으로 실행한 결과 입니다.


setUp(), tearDown()에 대해 정정


우선 제대로 알지 못하고 짐작으로 퉁~ 친점 죄송합니다. 앞서 스터디상에서 설명드릴때 setUp()메서드와 tearDowm()메서드 대신
어노테이션 사용하는데 @BeforeClass @AfterClass를 쓴다고 말했습니다.
정정 : @Before 와 @After 입니다.

import junit.framework.*;
import java.util.*;

public class Tester extends TestCase {
      public Tester(String name) {super(name);}
      private List list = new ArrayList();
      public void testFirst() {
            list.add("one");
            assertEquals(1, list.size());
      }
      public void testSecond() {
            assertEquals(0, list.size());
      }
}

위에 간단한 소스를 적어 놓았습니다.
testFirst() 메서드에선 list에 "one" 을 추가하고 list의 사이즈가 1과 같은지 를 묻고 있습니다.
testSecond() 메서드에도 list의 사이즈를 0과 같은지 묻고있습니다.
결과는 직접실행해 보시기 바랍니다.(추측후 실행해보시면 더욱 재미있으실 겁니다.)

JUnit 관련 검색중 재미있는 글을 읽었습니다.
원문 :
http://martinfowler.com/bliki/JunitNewInstance.html

JUnit에서는 테스트메서드 호출시마다 인스턴스를 새로 만든다는 내용의 글입니다. JUnit에서는 분리를 중요하게 여겨서
테스트시 자신의 테스트가 정작 다른테스트의 오류 때문에 방해를 받아선 안된다. 그래서 분리의 원칙에 따라 테스트
메서드 호출마다 새로운 인스턴스를 만든다는게 글의 요지입니다.

이미 알고 계신 내용이라면 김이 빠지실지도 모르겠습니다만 처음 알게된 저로선 작은 테스트를 해보기로 하겠습니다.
초심으로 돌아가서 static 초기화와 인스턴스초기화, 생성자 main()메서드 등등의 호출순서
처음으로 Java라는 것을 접하고 나열한 위의 ~ 호출순서때문에 고민했던 기억을 되살려 보시기 바랍니다.
(고민안하셨다면 천재일지도...;;; )

package testJunit;

public class testClass01 {

	static{
		System.out.println("static 초기화 제일먼저 호출");
	}
	{System.out.println("인스턴스 초기화 호출");}

	public void testMethod01() {
		System.out.println("testMethod01 호출");
	}
	public void testMethod02() {
		System.out.println("testMethod02 호출");
	}
	public static void main(String[] args) {

		System.out.println("main 호출");
		System.out.println("인스턴스 생성전");
		testClass01 tc01 = new testClass01();
		System.out.println("인스턴스 생성후");
		tc01.testMethod01();
		tc01.testMethod02();

	}

}

결과값

static 초기화 제일먼저 호출
main 호출
인스턴스 생성전
인스턴스 초기화 호출
인스턴스 생성후
testMethod01 호출
testMethod02 호출


네 알고계신 내용을 복습차원에서 써 놓았습니다.
JUnit에서의 호출수순도 이러한지 살펴볼 시간입니다.

package testJunit;

import static org.junit.Assert.assertEquals;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.JUnitCore;

public class SampleTest02 {

    static {
        System.out.println("static 초기화 출력");
    }
    {
        System.out.println("인스턴스 초기화 출력");
    }
    public SampleTest02() {
        System.out.println("생성자 출력");
    }
    @BeforeClass
    public static void doBeforeClass() {
        System.out.println("@BeforeClass 출력");
    }
    @Before
    public void doBefore() {
        System.out.println("@Before 출력");
    }
    @Test
    public void testOne() {
        System.out.println("@Test1 출력");
    }
    @Test
    public void testTwo() {
        System.out.println("@Test2 출력");
    }
    @After
    public void doAfter() {
        System.out.println("@After 출력");
    }
    @AfterClass
    public static void doAfterClass() {
        System.out.println("@AfterClass 출력");
    }
    public static void main(String[] args) {
	System.out.println("main 호출");
	System.out.println("인스턴스 생성전");
        JUnitCore.main(SampleTest02.class.getName());
	System.out.println("인스턴스 생성후");
    }
}

결과값

static 초기화 출력
main 호출
인스턴스 생성전
JUnit version 4.5
@BeforeClass 출력
.인스턴스 초기화 출력
생성자 출력
@Before 출력
@Test1 출력
@After 출력
.인스턴스 초기화 출력
생성자 출력
@Before 출력
@Test2 출력
@After 출력
@AfterClass 출력

Time: 0

OK (2 tests)





  • SampleTest02 클래스가 로드되면서 static초기화 호출
  • main()메서드 호출
  • @BeforeClass메서드 호출
  • 이하의 내용이 테스트메서드 호출마다 반복됩니다.
    • 인스턴스 초기화 호출
    • @Before메서드 호출
    • @Test메서드 호출
    • @After메서드 호출
  • @AfterClass메서드 호출

@BeforeClass, @Before, @After, @AfterClass의 차이점도 아실 수 있었습니다.

assertThat의 사용법

JUnit4.4버젼에서 추가된 assertThat()은 다른 단정메서드보다 더 범용적으로 활용이 가능할 것 같습니다.

assertThat
public static <T> void assertThat(T actual, org.hamcrest.Matcher<T> matcher)
public static <T> void assertThat(java.lang.String reason, T actual, org.hamcrest.Matcher<T> matcher)

assertThat()메서드에 인수로 지정되어 있는 org.hamcrest.Matcher 클래스는 비교처리를 위해 추가된 클래스입니다.
비교 처리를 위해 import static 해두시면 편하실 겁니다.
(ex : AllOf, AnyOf, DescribedAs, Is, IsAnything, IsEqual, IsInstanceOf, IsNot, IsNull, IsSame)

assertThat의 사용예

assertThat(x, is(3));
assertThat(x, is(not(4)));
assertThat(responseString, either(containsString("color")).or(containsString("colour")));
assertThat(myList, hasItem("3"));
  • 기존의 단정메서드를 사용하는 것보다 assertThat을 사용할때 가독성이 높고 무엇을 비교하는지 쉽게 알 수있다.
  • 기존의 단정메서드를 사용해 실패했을때 보다 이해하기가 쉽다. (실패메세지의 변화)
  • 비교처리를 스스로 추가해 사용 할 수 있다.

assertTrue failure Message

assertTrue(responseString.contains("color") || responseString.contains("colour"));
// ==> failure message:
// java.lang.AssertionError:

assertThat failure Message

assertThat(responseString, anyOf(containsString("color"), containsString("colour")));
// ==> failure message:
// java.lang.AssertionError:
// Expected: (a string containing "color" or a string containing "colour")
//      got: "Please choose a font"

assumeThat의 사용법

JUnit4.4에서는, org.junit.Assume 클래스가 추가되었습니다. Assume는 테스트하기 전에
전제조건을 밝히기 위한 클래스입니다. 동작은 앞서 설명한 AssertThat()메서드와 거의 동등합니다.

assertThat()의 차이점은 assumeThat()메서드로 검증을 실패하더라도 JUInit프레임워크의 테스트가
성공한 것으로 처리합니다. Assume은 테스트를 하기위한 전제조건을 테스트하는 기능입니다.

package Junit4_X;

import static org.junit.Assert.*;
import static org.junit.Assume.*;
import static org.hamcrest.CoreMatchers.*;
import org.junit.Test;

public class TestSample01 {

	@Test
	public void testMethod01() {
		assumeThat(0 ,is(0));
		assertThat(0, is(0));
		assertThat(0 ,is(1));
	}
}

만약 전제조건에서 에러가 발생하더라도, 후의 계속되는 테스트에는 무의미하다고 판단해 테스트 전체에 대해서
영향을 미치지 않게 하여 테스트를 성공하게 합니다. 다양한 환경에서 테스트할 필요가 있는 경우,
환경의존테스트 (OS종류에 따른 테스트등)를 만들고 싶을때가 있을것이라고 생각합니다.
그럴때 이용하시면 편리할 듯 합니다.

package Junit4_X;

import static org.junit.Assert.*;
import static org.junit.Assume.*;
import static org.hamcrest.CoreMatchers.*;
import org.junit.Test;

public class TestSample01 {

	@Test
	public void testMethod01() {
		assumeThat(0 ,is(0));
		assertThat(0, is(0));
                assumeThat(0 ,is(1));
		assertThat(0 ,is(1));
	}
}


Posted by B정상
JAVA/Unit Test2009. 2. 16. 11:35

뜬금없는 API입니다. 이부분은 단위테스트 with JUint의 Chapter3에 관한 내용입니다.
3장의 내용은 단정메서드, 테스트 조합, 예외및 이름규칙으로 나뉘어 있습니다.
3장에서 다루고 있는 내용을 좀 의도하지 않은 내용으로 가는건지 모르겠습니다만
3.8 버젼과 4.5 버젼의 비교정리할까 하여 기본이 되는 API 화면을 캡쳐해 보았습니다.

단정 메서드

테스트 대상이 되는 메서드가 제대로 동작하는지 아닌지 판단하도록 돕는 헬퍼메서드가 있습니다.
보통 이런 메서드들을 통틀어 단정메서드라고 부릅니다.

Method
assertArrayEquas(String message ,java.lang.Object[] expecteds ,java.lang.Object[] actuals)
assertEquals(String message ,expected ,actual)
assertEquals(String message ,expected ,actual ,tolerance)
assertNull(String message ,Object obj)
assertNotNull(String message ,Object obj)
assertTrue(String message , boolean condition)
assertFalse(String message , boolean condition)
fail(String message

왠지 메서드의 명이 중요하다라는 생각이 들지 않으신지요? 메서드명만으로도 무슨일을 하는지 느낌이 오시죠.
assertEquals(String message ,expected ,actual) 을 보면 expected에는 기대하는 값,
actual은 테스트의 대상이 되는 값을 넣습니다. message는 테스트가 실패할 경우 출력될 문자열이며, 선택사항입니다.

assertEquals(String message ,expected ,actual ,tolerance)의 tolerance는 두 비교값이 얼마나
동일해야 하는지를 나타내는 오차한계입니다. 실수형의 데이터비교시 사용하게 됩니다.
ex:assertEquals(3.33, 10.0/3.0, 0.01)

fail은 테스트 실패시 메세지가 있으면 출력해 줍니다.

@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"};
         assertArrayEquals("the two arrays should not be equal", actual, var);		
      }

새롭게 추가된 assertArrayEquals() 입니다. 배열의 값이 동일한지 비교처리 가능합니다.

테스트 조합

TestClassOne.java
package chap3;

import junit.framework.TestCase;

public class TestClassOne extends TestCase {

	public TestClassOne(String method) {
		super(method);
	}
	
	public void testAddtion() {
		assertEquals("testAddtion : true",4, 2+2);
	}
	
	public void testSubtraction(){
		assertEquals("testSubtraction : true",0, 2-2);
	}
}
TestClassTwo.java
package chap3;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

public class TestClassTwo extends TestCase {

	public TestClassTwo(String method) {
		super(method);
	}
	public void testLongRunner() {
		TSP tsp = new TSP();
		assertEquals(2300, tsp.shortestPath(50));
	}
	public void testShortTest() {
		TSP tsp = new TSP();
		assertEquals(140, tsp.shortestPath(5));
	}
	public void testAnotherShortTest() {
		TSP tsp = new TSP();
		assertEquals(586, tsp.shortestPath(10));
	}
	
	public static Test suite() {
		TestSuite suite = new TestSuite();
		suite.addTest(new TestClassTwo("testShortTest"));
		suite.addTest(new TestClassTwo("testAnotherShortTest"));
		
		return suite;
	}
}
TestClassComposit.java
package chap3;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

public class TestClassComposit extends TestCase {

	public TestClassComposit(String method) {
		super(method);
	}
	
	public static Test suite() {
		TestSuite suite = new TestSuite();
		suite.addTestSuite(TestClassOne.class);
		suite.addTest(TestClassTwo.suite());
		return suite;
	}
	

}

모아모아 테스트입니다~.
구구절절 설명하고 있지만 역시 한장소에서 한번에 테스트하고 싶다는 말이더군요.

TestSuite테스트 조합단점 : 저는 TestSuite만으로 메서드들을 모아 비지니스프로세스 순으로 테스트가 가능할 것이다 라고
생각하고 있었습니다. 근데 메서드 3개정도를 모아놓고 실험을 했는데 절차적으로 테스트하는 것도
아니고 전혀 엉뚱한 방향으로 테스트가 흘러가더군요.
테스트 각각이 독립적이기 때문에 순서에 상관없이 테스트가 실행된다는 사실...
그러므로 테스트조합으로는 비지니스프로세스순의 테스트는 불가능하다 입니다.
앞으로 배우게 될 +모의객체+를 이용하여야 가능하다는 얘기를 듣고 충격을 먹었습니다.

액기스만 모아 볼까요...

public static Test suite() {
	TestSuite suite = new TestSuite();
	suite.addTestSuite(TestClassOne.class);
	suite.addTest(TestClassTwo.suite());
	return suite;
}
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;

@RunWith(Suite.class)
@SuiteClasses({TestClassOne.class,
               TestClassTwo})
public class TestClassComposit {
    ...
}

JUnit의 @RunWith 어노테이션은 프레임워크에 내장된 러너가 아닌 다른 러너를 통해 특정 테스트 클래스를 손쉽게 실행할 수 있습니다.
@RunWith 어노테이션에서 지정해야 하는 Suite라는 이름의 스위트러너를 번들로 포함합니다.
또 테스트 스위트를 나타내기 위한 클래스 목록을 매개변수로 취하는 @SuiteClasses라는 주석을 제공합니다.

  1. setUp();
  2. testMethod();
  3. tearDown();

메서드는 각각 테스트 전후에 환경설정및 정리시에 호출하여 사용합니다.
굳이 예를 들자면, 데이터베이스 연결객체가 필요하다고 할때 각테스트할 메서드에 일일이 넣지않고 위의 메서드를 활용하면 좋겠죠.

protected void setUp(){
      dbConn= new Connection("Oracle","1521","ID","PASS");
      dbConn.connect();
}
protected void tearDown(){
      dbConn.disconnect();
}
public void testMethod() {
      ....
}

최신판에선 어떻게 사용하는지 살펴봐야죠~

import org.junit.AfterClass;
import org.junit.BeforeClass;


@Before
protected void setConn(){
      dbConn= new Connection("Oracle","1521","ID","PASS");
      dbConn.connect();
}

@After
protected void disConn(){
      dbConn.disconnect();
}

@Test
public void getMethod() {
      ....
}

느낌상 setUp(), tearDown() 내용도 사용방법도 거의 비슷합니다.
어노테이션이 갖는 장점은 이전에 JUnit가 갖는 일종의 규칙에 대한 지식이 없어도 이 메서드가
어떤 역활을 담당하는지 유추할 수 있다는 겁니다.

간단하게 JUnit에 대해서 살펴봤습니다.


Posted by B정상
JAVA/Unit Test2009. 2. 16. 11:33

머리로 아는 내용을 설명한다는 것이 정말 답답하더군요. 진도도 나가지 않고 답답한 맘뿐입니다.
차라리 단위테스트 설명을 올리는 것보다 Junit 테스트를 먼저해서 올렸으면 좀더 수월하게 했을지로 모른다는 생각이 드네요.
썰은 이쯤에서 마치고 테스트로 들어 가겠습니다.

  • 어떤 코드건 작성하기 전에 실패하는 자동화 테스트를 작성하라.
  • 중복을 제거하라

TDD의 들어가는 글에 나와있는 규칙입니다. 그리고 위의 두개가 TDD의 주제 이기도 합니다.
위에서 열거한 자동화된 테스트를 위한 툴이 저희가 공부할 Junit입니다.

JUnit은 현재 4.5버젼까지 나왔으며 단위테스트 with JUnit 에서 사용하는 버젼은 3.8x을 사용하고 있습니다.

  • 3.8x 버젼의 권장사항
    1. TestCase를 확장한다.
    2. 메서드명은 test로 시작해야 한다.
      만약 테스트 하려는 메서드가 createMethod()라면 testCreateMethod()로 명명할 것을 권장한다.

책에 부록으로 있는 Eclipse에서 Junit사용하기를 보시면 신호등관련 프로그램의 테스트가 나와 있습니다.

package TrafficLightModel;

import junit.framework.TestCase;


public class TrafficLightModelTest3_X extends TestCase {

      public void getTrafficLightModel() {

            TrafficLightModel a = new TrafficLightModel();
            assertTrue(a.getRed());
            //assertTrue(a.getYellow());
            //assertTrue(a.getGreen());

      }

}

얼핏 보아도 쉬운 내용임을 알 수 있습니다. getRed()메서드를 포함한 TrafficLightModel 클래스가 구현되어 있다면,
Junit를 실행하기 전까진컴파일에 있어서도 아무 문제를 일으키지 않습니다.


Failure_Trace 창의 에러메세지를 보시면 test를 찾을 수 없다고 나옵니다.
getTrafficLightModel() 메서드의 이름을 testTrafficLightModel() 로 바꿔 주시면 반가운 초록막대를 보실 수 있습니다.
즉, 권장사항이라고 얘기했지만 위의 두 규칙을 지키지 않으면 테스트 할 수 없는 것입니다.

Junit 3.8x 버젼때의 제약은 4.로 넘어가면서 자바5의 어노테이션과 만나 위의 권장사항은 따르실 필요는 없게 되었습니다.
물론 기존의 테스트소스를 Junit 최신판으로 다운받아 실행하는데 문제는 없습니다.
그저 좀더 간편해 졌기에 맛보기용으로 조금 올려 봅니다.

package TrafficLightModel;

import org.junit.Test;
import static org.junit.Assert.*;

public class TrafficLightModelTest4_X {

	@Test
	public void getTrafficLightModel() {
		TrafficLightModel a = new TrafficLightModel();
		assertTrue(a.getRed());
	}
}

클래스 계층구조는 더이상 필요하지 않고, 테스트로 작동할 메서드도 새롭게 정의된 @Test 어노테이션만 기술하시면 됩니다.

다시 책으로 돌아가 2장의 첫테스트와 만나 보겠습니다.
2장의 첫테스트는 정수배열을 받아들여 그중에서 큰값을 리턴해주는 프로그램입니다.

package first;

public class Largest {
	
	public int largest(int[] list){
		int max = Integer.MAX_VALUE;
		for (int index=0; index <list.length;index++) {
			if (list[index] > max)
				max = list[index];
		}
		return max;
	}
	
	public static void main (String args[]) {
	    Largest l = new Largest();
		System.out.println(l.largest(new int[] {7,8,-20,0,8}));
	}
}

저의 테스트 방식입니다. main()메서드를 사용해서 리턴값을 확인한다거나 println()문을 사용하여 제가 만들어 놓은 값을 확인합니다.
요즘도 값을 추출하거나 특정단어를 인식하는 간단한 메서드를 만들어 테스트할때 많이 사용합니다.

그리고 보통의 경우 테스트는 다음과 같은 방식으로 진행될 것입니다.

  1. 재빨리 테스트를 하나 추가한다.
  2. 모든 테스트를 실행하고 새로 추가한 것이 실패하는지 확인한다.
  3. 코드를 조금 바꾼다.
  4. 모든 테스트를 실행하고 전부 성공하는지 확인한다.

위의 말중에 재빨리와 실패라는 단어가 눈에 거슬릴지도 모르겠습니다만 잠시 무시해 주세요.

Lagest 클래스로 눈을 돌려 저희가 일련의 배열에서 큰값을 리턴해 주는 메서드를 작성하려 할때 메서드를 만들어
편한데로 확인하려 할것입니다. 위의 메서드에서도 원했던 값은 배열중 제일큰 8이란 값을 리턴해주길 바랬지만
끔찍하게도 2,147,483,647 이란 값을 리턴해 줍니다.

절대 실패할것이 아니고 단번에 제가 원한 답이 나와주길 바랍니다만 실패할때가 많습니다.

우연찮게 성공했다고 하여도 한번에 여러개의 값을 동시에 입력, 삭제할때 혹은 동일 값이 들어가 있거나 등의
앞장에서도 살짝쿵 설명했지만 경계조건의(대표값: null or 0)의 사이에서 의도치는 않았겠지만 실패요소들을
상당수 안고 있습니다.

위의 2번째 실패하는지 확인한다의 3번째 코드를 바꾼다 횟수가 많았던 만큼 현업 혹 윗상사의 눈밖에 날 위험은 현저히 줄어들겁니다.
2~3의 단계 무한반복후 의도한 값이 제대로 나오면 보통의 테스트는 여기서 끝나게 됩니다.
허나 하나 더 남아 있습니다.

5. 리팩토링을 통해 중복을 제거한다.

뭔가 앞에 말이 붙긴 했지만 어디서 봤던 말입니다. 중복을 제거 한다. 무슨 중복을 제거하는지는 차차 보시게 될듯합니다.
위에 써놓은 말은 토시하나 안틀리고 테스트 주도 개발에 나오는 내용입니다.
리팩토링을 통한 중복제거는 앞으로 소개하는 것으로 하고 넘어가겠습니다. (제 레벨이 아직;;; )

package first;

import junit.framework.TestCase;

public class LargestTest3_X extends TestCase {

	Largest l = new Largest();
	public void testGetLargest() {
		
		assertEquals(8,l.largest(new int[] {7,8,-20,0,8}));
	}
}

JUnit에서 다음같은 코드로 테스트를 하셨다면 Failure Trace창에서 이란 말을 보실 수 있습니다.

junit.framework.AssertionFaildError:expected:<8> but was:<2147483647>
at first.LargestTest3_X.testGetLargest(LargestTest3_X.java:10)

int max = Integer.MAX_VALUE;
를 사용한 부분을 max = 0으로 수정을 한 후 테스트를 하면 또다시 우리를 기쁘게 해주는 초록막대를
확인가능합니다.

그러나 문제가 있습니다. 배열의 정수가 모두 -값이라면 얘기가 달라집니다.
배열에는 (-41,-6,-30,-22) 값이 있을때 제가 원한 -8값을 돌려 주는 것이 아니라 0을 리턴하니
main()메서드로 얼핏 확인했다며 놓치고 지나칠 수 있었을 문제였습니다만

junit.framework.AssertionFaildError:expected:<8> but was:<0>
at first.LargestTest3_X.testGetLargest(LargestTest3_X.java:11)

네~ 실패했습니다. max = 0 이었던 값도 문제가 있었습니다.
max의 초기값을 음수보다 작게 만들어야 문제가 없음이 판명되었습니다. Integer.MIN_VALUE를 사용해야
다시 모두가 행복해지는 결과를 볼 수 있습니다.

공배열을 넣고 원하는 결과를 리턴 받을 수 있을까요??

package first;

import junit.framework.TestCase;

public class LargestTest3_X extends TestCase {

	Largest l = new Largest();
	public void testGetLargest() {
		assertEquals(8,l.largest(new int[] {7,8,-20,0,8}));
		assertEquals(-6,l.largest(new int[] {-41,-6,-30,-22}));
	}
	public void testGetEmpty() {
		l.largest(new int[] {});
		fail("테스트를 실패처리함.");
	}
}

네~ 실패했습니다. 책에 나와있는 설명으로 보면

' 단지 테스트를 생각해 보는것만으로 코드의 설계를 바꿔야한다는 것을 알아냈다는 사실에 주목하라.
이것은 전혀 특별한 일이 아니며, 실제로 우리가 테스트를 이용해 얻고자한 이득중의 하나이다.'

제가 플젝현장에서 메서드작성중 이런 결과를 얻어내고 메서드에 반영조치 했다면 충분히 특별하고
충분히 자랑질할 일이었을 듯 합니다만 책에선 특별한 일이 아니고 통상의 일이라 하니 그렇게 느낄때까지
사용할 수 밖에 없겠습니다.

Largest.java
int max = Integer.MIN_VALUE;
	if (list.length == 0) {
	    throw new RuntimeException("공배열입니다.");
         }

테스트에서 발생하는 * 예상된 예외에 대한 처리 *를 할때 fail을 사용한다고 합니다.

LargestTest3_X.java
public void testGetEmpty() {
		try{
			l.largest(new int[] {});
			fail("테스트를 실패처리함.");
		}catch(Exception e){
			assertTrue(true);
			
		}
	}

코드를 다음과 같이 수정후 다시한번 행복의 초록막대를 보실 수 있습니다.

package first;

import org.junit.Test;
import static org.junit.Assert.*;

public class LargestTest4_X {
	Largest l = new Largest();
	
	@Test
	public void GetLargest() {
		assertEquals(8,l.largest(new int[] {7,8,-20,0,8}));
		assertEquals(-6,l.largest(new int[] {-41,-6,-30,-22}));
	}
	@Test (expected=RuntimeException.class)
	public void GetEmpty() throws Exception{
			l.largest(new int[] {});
	}
}

위의 코드는 LargestTest3_X.java와 동일합니다.
try/catch 문을 사용하지 않고 expected 매개변수를 사용함으로 Exception 처리가 조금 달라졌습니다.

Posted by B정상
JAVA/Unit Test2009. 2. 16. 11:30

단위테스트란?

테스트 대상이 되는 코드 기능의 아주 작은 특정 영역을 실행해 보는, 개발자가 작성한 코드 조각이다.
대개 단위 테스트는 특정 상황에서 특정메서드를 시험해 본다.
어떤 코드 조각이 개발자가 생각하는 대로 동작하는지 증명하기 위해 수행하는 것이다.

기능테스트에 속한다. 개발자에 의한 개발자를 위한 테스트로 필수적이긴 하지만, 검증툴로서는 불충분하다.

테스트의 6가지 영역 - Right-BICEP


  • Right - 결과가 옳은가?
    예상한 결과가 옳은지 살펴보는 결과의 유효성 검사를 하는 것.
    이 코드가 옳게 동작한다면, 어떻게 그것을 알 수 있는가? 에 대해 요구사항이 완벽하지 않고
    대답이 명확하지 않다면 적어도 개발자 관점에서 몇가지는 생각해 낼 수 있다.
    물론 추측한 내용은 사용자 피드백에 의해 다듬어 조정해 가는 과정을 거쳐야 할 것이다.
    즉 어느시점에서든 생각한 대로 동작한다는 것을 증명할 수 있어야 한다.

  • B - 모든경계조건이 CORRECT 한가
    버그는 '경계조건' 근처 즉 코드가 평소의 루틴과 다르게 동작하는 조건에서 많이 발생한다.

    • Conformance - 형식일치
      이메일, 전화번호, 계좌번호, 파일이름등 형식화된 데이터부터 여러연결구조의 데이터등을
      테스트 하여 기대한 형식과 일치하는지 확인해야 한다.
    • Ordering - 순서
      고려해야할 또 하나의 영역은 큰데이터 모음에서 데이터의 순서나 데이터의 한부분의 위치이다.
      어떤 검색루틴이라 해도 반드시 대상이 시작부분이나 끝 부분에 있는 조건을 테스트 받아야 한다.
      많은 버그들을 이런 식으로 찾아낼 수 있다.
    • Range - 범위
      범위는 어떤 변수형이 필요하거나 원하는 값보다 범위를 허용하는 상황을 포괄적으로 함축하는 단어다.
      좋은 객체지향 설계에서는 나이나 방위처럼 한계가 있는 정수형 값(나이를 20,000살이라 입력하거나
      방위의 값을 365도로 입력하는 것 처럼)을 저장할때에는 그대로의 기본형을 사용하지는 않는다.
    • Reference - 참조
      메서드가 자기 영역을 벗어난 어떤 것들을 참조하는가? 외부 의존성이 있는가?
      클래스가 가져야 하는 상태는? 그 메서드가 제대로 동작하려면 그밖에 어떤 조건을 갖춰야 하는가?
      ex) 선언은 되었으나 정의되지 않은 전역변수
    • Existence - 존재성
      ex) null값을 갖는 List의 size() 메서드 호출(null,0,'')
      종료된 세션의 접근
    • Cardinality - 개체수
    • Time - 시간
      ex) 상대시간 (시간적순서)
      절대시간 (경과한 총 계산 시간)
      동시성 문제

  • I - 역관계확인
    논리적 역(Inverse)을 적용하여 검증해 볼 수 있다.

  • C - 다른 수단을 이용한 교차확인
    다른 수단을 이용해서 메서드의 결과를 교차 확인 할 수도 있다. 보통 어떤 값을 계산할 때에는 방법이 한 가지만 있는 건 아니다.
    어떤 한 알고리즘을 선택하는 이유는 그것이 더 좋은 성능을 내거나, 또는 그밖에 다른 좋은 특성이 있기 때문이다.
    제품에는 당연히 제일 좋은 것을 사용하겠지만, 테스트 시스템에서는 결과를 교차 확인하기 위해
    다른 알고리즘을 사용해 볼 수도 있다. 이 기법은 어떤 일을 수행하기 위한 알려진 방법이 있는데,
    제품 코드에 사용하기에는 지나치게 느리거나 유연성이 없는 경우에 특히 유용하다.

  • E - 에러 조건을 강제로 만들어 내기
    현실세계에서는 에러가 발생한다. 디스크가 꽉차고, 네트워크는 끊어지거나 프로그램은 갑자기 멈춘다.
    이런 에러를 일으켜 현실 세계의 문제들을 제대로 처리한다는 것을 테스트할 수 있어야 한다.

  • P - 성능특성
    성능은 그자체가 아니라 입력양이 많아지고, 문제가 복잡해지면서 성능이 변하는 경향을 말하는 것이다.
Posted by B정상
JAVA/Unit Test2008. 9. 19. 10:08

EasyMock 2.3 기본문서

2.3버전을 위한 문서(2007년 7월 10일)
© 2001-2007 OFFIS, Tammo Freese, translated by Lee DongGuk

EasyMock 2는 인터페이스를 위한 모의 객체를 사용하도록 쉬운 방법을 제공하는 라이브러리이다. EasyMock 2는 MIT 라이센스를 따른다..

모의 객체는 도메인 코드의 행위 일부를 가장하고 정의된 것처럼 사용되고 있는지를 체크할수 있다. 도메인 클래스는 모의 객체의 협력자(collaborators)를 가장하여 격리되어 테스트될수 있다.

모의 객체를 작성하고 유지하는 것은 에러를 만드는 종종 지루한 작업이 되곤 한다. EasyMock 2는 동적으로 모의 객체를 생성한다. 그래서 모의 객체를 작성할 필요도 생성되는 코드도 없다.

EasyMock 2 를 사용함으로 인해 얻는 이득

  • 직접 손으로 작성한 모의 객체 클래스가 필요하지 않다.
  • 리팩토링 작업에도 안전한 모의 객체 지원 : 테스트 코드는 메소드명을 바꾸거나 메소드 파라미터를 재정렬하더라도 런타임시 문제가 발생하지 않는다.
  • 리턴값과 예외 지원
  • 하나 이상의 모의 객체를 위해 메소드 호출의 순서를 체크하도록 지원

EasyMock 2 가 가지는 결점

  • EasyMock 2 는 JDK 5.0 이상에서만 작동한다.

EasyMock은 디폴트로 인터페이스를 위한 모의 객체의 생성만을 지원한다. 클래스를 위한 모의 객체를 생성하고자 하는 사람들을 위해 EasyMock 홈페이지에서 사용가능한 확장 패키지를 얻을수 있다.

설치

  1. Java 2 (최소한 5.0버전) 가 필요하다.
  2. EasyMock zip(easymock2.3.zip) 파일의 압축을 해제한다. 이 압축파일은 easymock2.3 디렉토리를 가지고 있으며 이 디렉토리안의 EasyMock jar파일(easymock.jar)을 클래스패스에 추가한다.

EasyMock 테스트를 실행하기 위해, tests.zip과 JUnit 4.1 jar 파일을 클래스패스에 추가하고 'java org.easymock.tests.AllTests'를 시작한다.

EasyMock의 소스코드는 src.zip 파일내 저장되어 있다.

사용법

소프트웨어 시스템의 대부분은 격리된체 작동하지 않지만 작업을 수행하기 위해 다른 부분과 협력한다. 대부분의 경우, 협력자를 신뢰하는 만큼 단위 테스트에서 협력자를 사용하는 것에 대해서는 고려하지 않는다. 우리가 협력자를 사용하는 것에 대해 고려한다면, 모의 객체는 격리된체 단위 테스트를 하도록 도와준다. 모의 객체는 테스트아래 단위의 협력자를 교체한다.

다음 예제는 Collaborator 인터페이스를 사용한다:

package org.easymock.samples;

public interface Collaborator {
void documentAdded(String title);
void documentChanged(String title);
void documentRemoved(String title);
byte voteForRemoval(String title);
byte[] voteForRemovals(String[] title);
}

이 인터페이스의 구현체는 ClassUnderTest 라는 이름의 클래스의 협력자이다:

public class ClassUnderTest {
// ...
public void addListener(Collaborator listener) {
// ...
}
public void addDocument(String title, byte[] document) {
// ...
}
public boolean removeDocument(String title) {
// ...
}
public boolean removeDocuments(String[] titles) {
// ...
}
}

클래스와 인터페이스 모두를 위한 코드는 아마도 samples.zip안의 org.easymock.samples 패키지에서 찾을수 있을것이다.

다음 예제들은 이 글을 읽는 사람이 JUnit 에 친숙하다고 가정하고 작성되었다. 비록 여기서는 3.8.1 버전의 JUnit를 사용하였지만 JUnit 4나 TestNG를 사용해도 잘 작동한다.

첫 모의 객체

지금 테스트 케이스를 빌드하고 EasyMock 패키지의 기능을 이해하기 위해 이것저것 해볼것이다. samples.zip은 이 테스트를 변형한 형태의 소스를 가진다. 첫번째 테스트는 존재하지 않는 문서를 제거하는 것이 협력자의 알림(notification)을 이끌어 내지 않는지를 체크한다. 여기서는 모의 객체의 정의 없이 테스트한다:

package org.easymock.samples;

import junit.framework.TestCase;

public class ExampleTest extends TestCase {

private ClassUnderTest classUnderTest;
private Collaborator mock;

protected void setUp() {
classUnderTest = new ClassUnderTest();
classUnderTest.addListener(mock);
}

public void testRemoveNonExistingDocument() {
// This call should not lead to any notification
// of the Mock Object:
classUnderTest.removeDocument("Does not exist");
}
}

EasyMock 2 를 사용하여 테스트 하기 위해 필요한 작업은 org.easymock.EasyMock 의 메소드를 정적 임포트(static import) 하는 것 뿐이다. 이 클래스는 EasyMock 2의 추상적이지 않고(non-internal) 비권장 되지 않은(non-deprecated) 클래스이다.

import static org.easymock.EasyMock.*;
import junit.framework.TestCase;

public class ExampleTest extends TestCase {

private ClassUnderTest classUnderTest;
private Collaborator mock;

}

모의 객체를 얻기 위해서 해야 할 작업은..

  1. 모의실험 하고자 하는 인터페이스를 위한 모의 객체를 생성하고
  2. 기대되는 행위를 기록한 뒤
  3. 모의 객체를 재생(replay) 상태로 전환한다.

첫번째 예제:

    protected void setUp() {
mock = createMock(Collaborator.class); // 1
classUnderTest = new ClassUnderTest();
classUnderTest.addListener(mock);
}

public void testRemoveNonExistingDocument() {
// 2 (we do not expect anything)
replay(mock); // 3
classUnderTest.removeDocument("Does not exist");
}

3 단계에서 활성화한 뒤, mock은 어떤 호출도 기대하지 않는 Collaborator 인터페이스를 위한 모의 객체이다. 이 말은 인터페이스의 어떤 메소드를 호출할려고 ClassUnderTest를 변경한다면, 모의 객체는 AssertionError를 던진다는 것을 의미한다:

java.lang.AssertionError: 
Unexpected method call documentRemoved("Does not exist"):
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44)
at $Proxy0.documentRemoved(Unknown Source)
at org.easymock.samples.ClassUnderTest.notifyListenersDocumentRemoved(ClassUnderTest.java:74)
at org.easymock.samples.ClassUnderTest.removeDocument(ClassUnderTest.java:33)
at org.easymock.samples.ExampleTest.testRemoveNonExistingDocument(ExampleTest.java:24)
...

행위 추가하기

두번째 테스트를 작성하자. 문서가 테스트하는 클래스에 추가되었다면, 인자로 문서의 제목을 가지는 모의 객체의 mock.documentAdded()를 호출하도록 기대한다:

    public void testAddDocument() {
mock.documentAdded("New Document"); // 2
replay(mock); // 3
classUnderTest.addDocument("New Document", new byte[0]);
}

replay 를 호출하기 전 기록(record) 상태에서, 모의 객체는 모의 객체처럼 작동하지 않지만 메소드 호출을 기록한다. replay를 호출한 후에는 실제로 기대한 메소드가 호출되었는지 체크하도록 모의 객체처럼 작동한다.

classUnderTest.addDocument("New Document", new byte[0]) 처럼 잘못된 인자를 가지는 메소드가 호출되었다면, 모의 객체는 AssertionError를 던질것이다:

java.lang.AssertionError: 
Unexpected method call documentAdded("Wrong title"):
documentAdded("New Document"): expected: 1, actual: 0
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44)
at $Proxy0.documentAdded(Unknown Source)
at org.easymock.samples.ClassUnderTest.notifyListenersDocumentAdded(ClassUnderTest.java:61)
at org.easymock.samples.ClassUnderTest.addDocument(ClassUnderTest.java:28)
at org.easymock.samples.ExampleTest.testAddDocument(ExampleTest.java:30)
...

기대하지 않은 호출을 위한 기대값을 만족하는 만큼, 빗나간 기대값이 보여진다. 메소드 호출이 너무 자주 수행되어도 모의 객체또한 문제가 된다.

java.lang.AssertionError: 
Unexpected method call documentAdded("New Document"):
documentAdded("New Document"): expected: 1, actual: 1 (+1)
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44)
at $Proxy0.documentAdded(Unknown Source)
at org.easymock.samples.ClassUnderTest.notifyListenersDocumentAdded(ClassUnderTest.java:62)
at org.easymock.samples.ClassUnderTest.addDocument(ClassUnderTest.java:29)
at org.easymock.samples.ExampleTest.testAddDocument(ExampleTest.java:30)
...

행위 검증하기

지금까지 다루지 않은 에러가 하나 있다. 행위를 선언한다면, 실제로 사용되는 것을 검증하고자 할것이다. 모의 객체의 호출되는 메소드가 없다면 현재 테스트는 진행될것이다. 선언된 행위가 사용되는지 검증하기 위해, verify(mock)를 호출해야만 한다:

    public void testAddDocument() {
mock.documentAdded("New Document"); // 2
replay(mock); // 3
classUnderTest.addDocument("New Document", new byte[0]);
verify(mock);
}

메소드가 모의 객체에서 호출되지 않는다면, 다음 예외를 보게된다:

java.lang.AssertionError: 
Expectation failure on verify:
documentAdded("New Document"): expected: 1, actual: 0
at org.easymock.internal.MocksControl.verify(MocksControl.java:70)
at org.easymock.EasyMock.verify(EasyMock.java:536)
at org.easymock.samples.ExampleTest.testAddDocument(ExampleTest.java:31)
...

예외 메시지는 빗나간 모든 기대값을 보여준다.

명시적인 호출 횟수 기대하기

지금까지의 테스트는 하나의 메소드 호출만을 고려했다. 다음 테스트는 이미 존재하는 문서의 추가가 적절한 인자를 가지는 mock.documentChanged()를 호출하도록 이끄는지를 체크할것이다. 확실히하기 위해 세번 체크한다.

    public void testAddAndChangeDocument() {
mock.documentAdded("Document");
mock.documentChanged("Document");
mock.documentChanged("Document");
mock.documentChanged("Document");
replay(mock);
classUnderTest.addDocument("Document", new byte[0]);
classUnderTest.addDocument("Document", new byte[0]);
classUnderTest.addDocument("Document", new byte[0]);
classUnderTest.addDocument("Document", new byte[0]);
verify(mock);
}

mock.documentChanged("Document") 반복을 피하기 위해, EasyMock은 단축된 형태를 제공한다. expectLastCall()에 의해 리턴된 객체에서 times(int times) 메소드로 호출 횟수를 명시할것이다. 그럼 코드는 다음과 같을것이다:

    public void testAddAndChangeDocument() {
mock.documentAdded("Document");
mock.documentChanged("Document");
expectLastCall().times(3);
replay(mock);
classUnderTest.addDocument("Document", new byte[0]);
classUnderTest.addDocument("Document", new byte[0]);
classUnderTest.addDocument("Document", new byte[0]);
classUnderTest.addDocument("Document", new byte[0]);
verify(mock);
}

메소드가 너무 자주 호출된다면, 메소드가 많은 횟수 호출된것을 알리는 예외를 얻게 된다. 첫번째 메소드 호출이 제한을 넘길때 즉시 실패하게 된다:

java.lang.AssertionError: 
Unexpected method call documentChanged("Document"):
documentChanged("Document"): expected: 3, actual: 3 (+1)
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44)
at $Proxy0.documentChanged(Unknown Source)
at org.easymock.samples.ClassUnderTest.notifyListenersDocumentChanged(ClassUnderTest.java:67)
at org.easymock.samples.ClassUnderTest.addDocument(ClassUnderTest.java:26)
at org.easymock.samples.ExampleTest.testAddAndChangeDocument(ExampleTest.java:43)
...

너무 호출이 적다면, verify(mock)AssertionError를 던진다:

java.lang.AssertionError: 
Expectation failure on verify:
documentChanged("Document"): expected: 3, actual: 2
at org.easymock.internal.MocksControl.verify(MocksControl.java:70)
at org.easymock.EasyMock.verify(EasyMock.java:536)
at org.easymock.samples.ExampleTest.testAddAndChangeDocument(ExampleTest.java:43)
...

리턴값을 명시하기

리턴값을 명시하기 위해, expect(T value) 로 기대되는 호출을 포장하고 expect(T value)에 의해 리턴된 객체의 andReturn(Object returnValue) 메소드를 사용하여 리턴값을 명시한다.

예제처럼, 문서 제거를 위한 작업량을 체크한다. ClassUnderTest가 문서 제거를 위한 호출이 이루어진다면, byte voteForRemoval(String title) 값을 위한 호출로 제거하기 위해 모든 협력자를 요청한다. 양수의 리턴값을 제거를 제안한다. 모든 값의 합이 양수라면, 문서는 제거되고 documentRemoved(String title)는 모든 협력자에게서 호출된다:

    public void testVoteForRemoval() {
mock.documentAdded("Document"); // expect document addition
// expect to be asked to vote for document removal, and vote for it
expect(mock.voteForRemoval("Document")).andReturn((byte) 42);
mock.documentRemoved("Document"); // expect document removal
replay(mock);
classUnderTest.addDocument("Document", new byte[0]);
assertTrue(classUnderTest.removeDocument("Document"));
verify(mock);
}

public void testVoteAgainstRemoval() {
mock.documentAdded("Document"); // expect document addition
// expect to be asked to vote for document removal, and vote against it
expect(mock.voteForRemoval("Document")).andReturn((byte) -42);
replay(mock);
classUnderTest.addDocument("Document", new byte[0]);
assertFalse(classUnderTest.removeDocument("Document"));
verify(mock);
}

리턴된 값의 타입은 컴파일시에 체크된다. 예제처럼, 다음 코드는 제공된 리턴값의 타입이 메소드의 리턴값과 일치하지 않는것처럼 컴파일되지 않을것이다:

    expect(mock.voteForRemoval("Document")).andReturn("wrong type");

리턴값을 셋팅하기 위한 객체를 가져오기 위해 expect(T value)를 호출하는 것 대신에, expectLastCall()에 의해 리턴된 객체를 사용하게 될것이다. .

    expect(mock.voteForRemoval("Document")).andReturn((byte) 42);

대신에

    mock.voteForRemoval("Document");
expectLastCall().andReturn((byte) 42);

를 사용할것이다. 컴파일시 타입 체크를 지원하지 않는것처럼 명시 타입은 라인이 너무 길때만 사용해야만 한다.

예외로 작업하기

던져질 예외를 명시(좀더 정확하게는 Throwables처리)하기 위해, expectLastCall()expect(T value)에 의해 반환된 객체는 andThrow(Throwable throwable) 메소드를 제공한다. 던져질 Throwable를 명시하기 위한 모의 객체를 호출한 후 기록(record) 상태에서만 호출될수 있다.

체크되지 않은 예외(RuntimeException, Error 와 모든 하위 클래스)는 모든 메소드에서 던져질수 있다. 체크된 예외는 예외를 실제로 던지는 메소드로부터 던져질수 있다.

리턴값이나 예외 만들기

때때로 값을 리턴하거나 실제 호출시 생성되는 예외를 던지기 위해 모의 객체를 선호할것이다. EasyMock 2.2부터, expectLastCall()expect(T value)에 의해 리턴되는 객체는 리턴값이나 예외를 만들기 위해 사용되는 IAnswer 인터페이스의 구현체를 명시하도록 andAnswer(IAnswer answer) 메소드를 제공한다.

IAnswer 콜백내부에서, 모의 호출에 전달된 인자는 EasyMock.getCurrentArguments() 를 통해 사용가능하다. 전달된 인자를 사용한다면, 파라미터 재정렬과 같은 리팩토링이 테스트에 문제를 야기할지도 모른다.

같은 메소드 호출을 위한 행위 변경하기

메소드를 위한 행위를 변경하는 것이 가능하다. times, andReturn, 과 andThrow 메소드는 연결(chain)될수 있다. 예를 들어, 다음 상황을 이루기 위해 voteForRemoval("Document")를 정의한다.

  • 처음 세번의 호출로 42를 리턴한다.
  • 그 다음 네번째 호출에서는 RuntimeException을 던진다.
  • -42를 리턴한다.
    expect(mock.voteForRemoval("Document"))
.andReturn((byte) 42).times(3)
.andThrow(new RuntimeException(), 4)
.andReturn((byte) -42);

호출 횟수 줄이기

기대한 호출 횟수를 줄이기 위해, times(int count) 대신에 사용할수 있는 추가적인 메소드가 있다:

times(int min, int max)
minmax 값 사이의 횟수만큼 호출
atLeastOnce()
적어도 한번 호출
anyTimes()
호출횟수에 제한이 없는

호출 횟수를 명시하지 않았다면, 한번만 호출된다. 명시적으로 이 상태를 유지하고자 한다면 once()times(1) 를 사용하면 된다.

엄밀한(Strict) 모의 객체

EasyMock.createMock()에 의해 리턴되는 모의 객체에서, 메소드 호출의 순서는 체크되지 않는다. 메소드 호출의 순서를 체크하는 엄밀한 모의 객체를 원한다면, 모의 객체를 만들기 위해 EasyMock.createStrictMock()를 사용하라.

기대하지 않은 메소드가 엄밀한 모의 객체에서 호출된다면, 예외 메시지가 처음 충돌이 발생한 지점에서 기대한 메소드 호출을 보여줄것이다. verify(mock) 는 빗나간(missing) 모든 메소드 호출을 보여준다.

순서 체크를 활성화하기 또는 비활성화하기

때때로 몇가지 호출의 순서만을 체크하는 모의 객체를 가질 필요가 있다. 기록(record) 상태에서, checkOrder(mock, true) 를 호출하여 순서 체크를 활성화하고 checkOrder(mock, false)를 호출하여 순서 체크를 비활성화할것이다.

엄밀한 모의 객체와 일반 모의 객체간에는 두가지 차이점이 존재한다:

  1. 엄밀한 모의 객체는 생성후 순서 체크가 가능해진다.
  2. 엄밀한 모의 객체는 리셋(reset)후 순서 체크가 가능해진다(모의 객체 재사용하기를 보라.).

인자 매처(Matchers)를 사용한 유연한 기대(Expectation)

모의 객체의 메소드 호출을 기대값과 일치시키기 위해, Object 인자는 equals() 메소드를 통해 비교된다. 이런 방법은 문제를 야기할수 있다. 예를 들어, 다음의 기대를 고려해보자:

String[] documents = new String[] { "Document 1", "Document 2" };
expect(mock.voteForRemovals(documents)).andReturn(42);

메소드가 같은 내용을 가진 또 다른 배열로 호출된다면, equals()는 객체가 배열과 일치하는지 비교하는 것으로 예외를 보게 될것이다:

java.lang.AssertionError: 
Unexpected method call voteForRemovals([Ljava.lang.String;@9a029e):
voteForRemovals([Ljava.lang.String;@2db19d): expected: 1, actual: 0
documentRemoved("Document 1"): expected: 1, actual: 0
documentRemoved("Document 2"): expected: 1, actual: 0
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44)
at $Proxy0.voteForRemovals(Unknown Source)
at org.easymock.samples.ClassUnderTest.listenersAllowRemovals(ClassUnderTest.java:88)
at org.easymock.samples.ClassUnderTest.removeDocuments(ClassUnderTest.java:48)
at org.easymock.samples.ExampleTest.testVoteForRemovals(ExampleTest.java:83)
...

오직 배열 일치가 미 호출을 위해 필요하다는 것을 명시하기 위해, EasyMock 클래스로부터 정적으로 임포트되는 aryEq 메소드를 사용할수 있다:

String[] documents = new String[] { "Document 1", "Document 2" };
expect(mock.voteForRemovals(aryEq(documents))).andReturn(42);

호출내 매처(matcher)를 사용하고자 한다면, 메소드 호출의 모든 인자를 위한 매처(matcher)를 명시해야만 한다.

사용가능한 두세개의 미리 정의된 인자 매처(matcher)가 있다.

eq(X value)
실제값이 기대값과 같다면 대응. 모든 원시타입과 객체타입에 사용가능.
anyBoolean(), anyByte(), anyChar(), anyDouble(), anyFloat(), anyInt(), anyLong(), anyObject(), anyShort()
어떤 값과도 대응. 모든 원시 타입과 객체타입에 사용가능.
eq(X value, X delta)
실제값이 델타값이 허용하는 값과 같다면 대응. floatdouble 타입의 값에서 사용가능.
aryEq(X value)
실제값이 Arrays.equals()에 따라 주어진 값과 같다면 대응. 원시타입 배열과 객체 타입 배열에 사용가능.
isNull()
실제 값이 null이라면 대응. 객체타입에 사용가능.
notNull()
실제값이 null이 아니라면 대응. 객체타입에 사용가능.
same(X value)
실제값이 주어진 값과 같다면(same) 대응 객체타입에 사용가능.
isA(Class clazz)
실제값이 주어진 클래스의 인스턴스이거나 주어진 클래스를 확장하거나 구현한 클래스의 인스턴스라면 대응. 객체타입에 사용가능.
lt(X value), leq(X value), geq(X value), gt(X value)
실제값이 주어진 값보다 작다면/작거나 같다면/크거나 같다면/크다면 대응. 모든 숫자 원시 타입과 Comparable 인터페이스를 구현한 객체 타입에 사용가능.
startsWith(String prefix), contains(String substring), endsWith(String suffix)
실제값이 주어진 값으로 시작한다면/포함한다면/끝난다면 대응. String 타입에 사용가능.
matches(String regex), find(String regex)
실제값/실제값의 부분문자열이 주어진 정규표현식에 일치한다면 대응. String 타입에 사용가능.
and(X first, X second)
firstsecond에 사용된 매처(matcher)가 모두 일치한다면대응. 원시타입과 객체타입에 사용가능.
or(X first, X second)
firstsecond에 사용된 매처(matcher)중 하나가 일치한다면 대응. 원시타입과 객체타입에 사용가능.
not(X value)
value 인자에 사용된 매처(matcher)가 일치하지 않는다면 대응.
cmpEq(X value)
실제값이 Comparable.compareTo(X o)에 따라 일치한다면 대응. 모든 숫자 형식의 원시 타입과 Comparable 인터페이스를 구현한 객체 타입에 사용가능.
cmp(X value, Comparator comparator, LogicalOperator operator)
연산자가 <,<=,>,>= or ==인 comparator.compare(actual, value) operator 0 라면 대응. 객체 타입에 사용가능.

스스로의 인자 매처(matcher) 정의하기

때때로 스스로의 인자 매처(matcher)를 정의하고자 바랄수 있다. 인자 매처(matcher)는 주어진 예외가 같은 타입을 가지고 동일한 메시지를 가진다면 예외가 일치할 필요가 있다는 것을 말해보자. 다음과 같은 방법으로 사용될수 있다:

    IllegalStateException e = new IllegalStateException("Operation not allowed.")
expect(mock.logThrowable(eqException(e))).andReturn(true);

이 기능을 위해서는 두가지 단계가 필요하다. 먼저 새로운 인자 매처(matcher)를 정의하고, 정적 메소드인 eqException를 선언해야 한다.

새로운 인자 매처(matcher)를 정의하기 위해, org.easymock.IArgumentMatcher 인터페이스를 구현한다. 이 인터페이스는 두개의 메소드를 가진다:matches(Object actual)는 실제 인자가 주어진 인자와 대응하는지를 체크한다. appendTo(StringBuffer buffer)는 주어진 문자열 버퍼에 인자 매처(matcher)의 문자열 표현을 뒤에 추가한다. 구현체는 일관적이다.

import org.easymock.IArgumentMatcher;

public class ThrowableEquals implements IArgumentMatcher {
private Throwable expected;

public ThrowableEquals(Throwable expected) {
this.expected = expected;
}

public boolean matches(Object actual) {
if (!(actual instanceof Throwable)) {
return false;
}
String actualMessage = ((Throwable) actual).getMessage();
return expected.getClass().equals(actual.getClass())
&& expected.getMessage().equals(actualMessage);
}

public void appendTo(StringBuffer buffer) {
buffer.append("eqException(");
buffer.append(expected.getClass().getName());
buffer.append(" with message \"");
buffer.append(expected.getMessage());
buffer.append("\"")");

}
}

eqException 메소드는 주어진 Throwable으로 인자 매처(matcher)를 생성해야만 한다. 정적 메소드인 reportMatcher(IArgumentMatcher matcher)를 통해 EasyMock에 알리고 값(대개 0, null 또는 false)을 리턴한다. 첫번째 시도는 다음처럼 보일것이다:

public static Throwable eqException(Throwable in) {
EasyMock.reportMatcher(new ThrowableEquals(in));
return null;
}

어쨌든, 예제 사용법의 logThrowable 메소드가 Throwable를 받는다면 작동한다. 그리고 RuntimeException처럼 명시된 어떤것도 요구하지 않는다. 후자의 경우 샘플 코드는 컴파일 되지 않을것이다:

    IllegalStateException e = new IllegalStateException("Operation not allowed.")
expect(mock.logThrowable(eqException(e))).andReturn(true);

구제(rescue)를 위한 Java 5.0: 파라미터와 리턴값으로 Throwable를 가지고 eqException를 정의하는 것 대신에, Throwable를 확장하는 일반적인 타입을 사용한다:

public static <T extends Throwable> T eqException(T in) {
reportMatcher(new ThrowableEquals(in));
return null;
}

모의 객체 재사용하기

모의 객체는 reset(mock) 메소드에 의해 리셋될수 있다.

메소드를 위한 스텁 행위 사용하기

때때로. 몇가지 메소드 호출에 응답하고자 모의 겍체를 바라지만 호출될때 호출되는 방법을 체크하고자 하지는 않는다. 스텁 행위는 andStubReturn(Object value), andStubThrow(Throwable throwable), andStubAnswer(IAnswer answer) 그리고 asStub() 메소드를 사용하여 정의된다. 다음의 코드는 voteForRemoval("Document")를 위해 42를 반환하고 다른 모든 인자를 위해서는 -1을 응답하는 모의 객체를 설정한다:

    expect(mock.voteForRemoval("Document")).andReturn(42);
expect(mock.voteForRemoval(not(eq("Document")))).andStubReturn(-1);

멋진(Nice) 모의 객체

createMock()에 의해 반환되는 모의 객체에서, 모든 메소드를 위한 디폴트 행위는 기대하지 않은 모든 메소드 호출을 위해 AssertionError를 던진다. 디폴트로 모든 메소드 호출을 허용하고 적절히 빈값(0, null or false)을 반환하는 "멋진(nice)" 모의 객체를 원한다면, 대신 createNiceMock()를 사용하라.

객체 메소드

3개의 객체 메소드인 equals(), hashCode()toString()을 위한 행위는 EasyMock 으로 생성된 모의 객체에서도 변경할수 없다.

모의 객체 사이의 메소드 호출 순서 체크하기

이 시점에, EasyMock 클래스의 정적 메소드에 의해 설정된 하나의 객체로 모의 객체를 보았다. 하지만 이러한 많은 정적 메소드는 모의 객체의 숨겨진 제어를 확인하고 위임한다. 모의 제어(Mock Control)는 IMocksControl 인터페이스를 구현한 객체이다.

    IMyInterface mock = createStrictMock(IMyInterface.class);
replay(mock);
verify(mock);
reset(mock);

대신에 다음과 같은 동등한 코드를 사용하게 될것이다:

    IMocksControl ctrl = createStrictControl();
IMyInterface mock = ctrl.createMock(IMyInterface.class);
ctrl.replay();
ctrl.verify();
ctrl.reset();

IMocksControl은 하나 이상의 모의 객체를 생성하도록 하고 모의 객체 사이의 메소드 호출 순서를 체크하는 것이 가능하다. 예제처럼, IMyInterface 인터페이스를 위한 두개의 모의 객체를 셋업하고 mock1.a()mock2.a() 를 순서대로 호출한다. 그리고 나서 mock1.c()mock2.c() 호출한다. 마지막으로 mock2.b()mock1.b()를 순서대로 호출한다:

    IMocksControl ctrl = createStrictControl();
IMyInterface mock1 = ctrl.createMock(IMyInterface.class);
IMyInterface mock2 = ctrl.createMock(IMyInterface.class);

mock1.a();
mock2.a();

ctrl.checkOrder(false);

mock1.c();
expectLastCall().anyTimes();
mock2.c();
expectLastCall().anyTimes();

ctrl.checkOrder(true);

mock2.b();
mock1.b();

ctrl.replay();

모의 객체 명명하기

모의 객체는 createMock(String name, Class toMock), createStrictMock(String name, Class toMock) 또는 createNiceMock(String name, Class toMock) 를 사용하여 만드는 것으로 명명할수 있다. 그 이름은 예외 메시지에서 보여질것이다.

이전버전과의 호환성

EasyMock 2는 Java 1.5를 위해 EasyMock 1.2를 사용한 테스트가 변경없이 작동하도록 하는 호환성 계층을 포함한다. 알려진 차이점은 실패할때 보일수 있다는 것이다. 실패 메시지와 스택 트레이스(stack trace)에 작은 변경사항이 있다. 실패는 JUnit의 AssertionFailedError 대신에 자바의 AssertionError를 사용하여 보고된다.

EasyMock 2.1은 너무 복잡해서 EasyMock 2.2에서 제거된 콜백 기능을 소개했다. EasyMock 2.2이후 IAnswer 인터페이스가 콜백을 위한 기능을 제공한다.

EasyMock 개발

EasyMock 1.0은 OFFIS의 Tammo Freese가 개발했다. EasyMock 개발은 다른 개발자와 기부회사를 허용하기 위해 소스포지에서 호스팅되고 있다.

다음에 나열된 분들을 포함하여 피드백이나 패치를 제공하는 수많은 분들에게 감사한다. Nascif Abousalh-Neto, Dave Astels, Francois Beausoleil, George Dinwiddie, Shane Duan, Wolfgang Frech, Steve Freeman, Oren Gross, John D. Heintz, Dale King, Brian Knorr, Dierk Koenig, Chris Kreussling, Robert Leftwich, Patrick Lightbody, Johannes Link, Rex Madden, David McIntosh, Karsten Menne, Bill Michell, Stephan Mikaty, Ivan Moore, Ilja Preuss, Justin Sampson, Markus Schmidlin, Richard Scott, Joel Shellman, Ji?i Mare?, Alexandre de Pellegrin Shaun Smith, Marco Struck, Ralf Stuckert, Victor Szathmary, Henri Tremblay, Bill Uetrecht, Frank Westphal, Chad Woolley, Bernd Worsch.

새로운 버전은 EasyMock 홈페이지에서 체크하고 EasyMock 야후! 그룹에 버그 리포팅이나 제안을 보내주세요. EasyMock 야후!그룹에 가입하고자 한다면 easymock-subscribe@yahoogroups.com로 메시지를 보내주세요.

EasyMock 버전 2.3 (2007년 7월 10일)

2.2버전 이후 변경사항:

  • 프랑스어 문서
  • 비교가능한(Comparable) 파라미터를 위한 Matchers
  • 10진수 비교 버그 픽스
  • 모의 객체 명명가능
  • 빌 미쉘의 ThreadLocal 버그 픽스 포함
  • EasyMock의 단위 테스트를 JUnit 4로 전환

2.1 버전 이후 변경사항:

  • andAnswer(IAnswer answer)andStubAnswer(IAnswer answer) 를 통해 호출시 기대한 호출을 위한 응답이 만들어질수 있다.
  • callback(Runnable runnable) 은 제거되었다. 콜백을 위해 andAnswer(IAnswer answer)andStubAnswer(IAnswer answer) 로 전환해 달라.
  • replay(), verify() 그리고 reset()은 인자로 여러개의 모의 객체를 받을수 있다.

2.0 버전 이후 변경사항:

  • 모의 객체로 전달된 인자는 EasyMock.getCurrentArguments() 를 통해 콜백에서 사용가능하다.
  • http://groups.yahoo.com/group/easymock/message/558에서 보고된 버그 픽스
  • 쓰지 않는 matchers가 명시된다면 일찍 실패한다.

1.2 버전 이후 변경사항:

  • 유연하고 리팩토링에 안전한 인자 matchers 지원
  • 모의 제어(mock control)은 하나의 모의 객체를 위해서는 필요하지 않다.
  • 스텁 행위는 디폴트 행위를 대체한다.
  • 하나 이상의 모의 객체를 위한 호출 순서 체크를 지원하고 순서 체크를 활성화하거나 비활성화할수 있다.
  • 콜백 지원
  • EasyMock 은 junit.framework.AssertionFailedError 대신에 java.lang.AssertionError 를 던진다. 그래서 테스팅 프레임워크에 의존적이지 않다. 테스팅 프레임워크로 JUnit 3.8.x, JUnit 4 그리고 TestNG를 사용할수 있다.
  • 비권장된(deprecated) 오래된 API
Posted by B정상
JAVA/Unit Test2008. 9. 11. 18:48

교육자료

Posted by B정상