JUnit로 검색한 결과 :: 시소커뮤니티[SSISO Community]
 
SSISO 카페 SSISO Source SSISO 구직 SSISO 쇼핑몰 SSISO 맛집
추천검색어 : JUnit   Log4j   ajax   spring   struts   struts-config.xml   Synchronized   책정보   Ajax 마스터하기   우측부분

회원가입 I 비밀번호 찾기


SSISO Community검색
SSISO Community메뉴
[카페목록보기]
[블로그등록하기]  
[블로그리스트]  
SSISO Community카페
블로그 카테고리
정치 경제
문화 칼럼
비디오게임 스포츠
핫이슈 TV
포토 온라인게임
PC게임 에뮬게임
라이프 사람들
유머 만화애니
방송 1
1 1
1 1
1 1
1 1
1

JUnit로 검색한 결과
등록일:2008-04-08 11:06:08
작성자:
제목:Eclipse 방식으로 단위 테스팅 하기 (한글)


Eclipse IDE에서 자바용 RMock 테스팅 프레임웍으로 jMock 강화하기

developerWorks
문서 옵션

JavaScript가 필요한 문서 옵션은 디스플레이되지 않습니다.

수평출력으로 설정

이 페이지 출력

이 페이지를 이메일로 보내기

이 페이지를 이메일로 보내기

영어원문

영어원문


제안 및 의견
피드백

난이도 : 중급

Michael Nyika, Software Engineer, MichaelDKelly.com

2007 년 7 월 24 일

소 스 코드 베이스를 테스트 할 수 있는 적합한 테스트 슈트가 필요하십니까? jMock은 훌륭한 테스팅 프레임웍으로서 자격을 갖추었습니다. 하지만 모든 상황에jMock이 다 맞는 것은 아닙니다. 애플리케이션에서 단위 테스트를 지원하는 커스텀 mock 객체를 어렵게 만들 필요 없이, RMock이 jMock과 조화롭게 작동하도록 하여 긍정적인 결과를 얻을 수 있습니다.
소셜 북마크

mar.gar.in mar.gar.in
digg Digg
del.icio.us del.icio.us
Slashdot Slashdot

Mock 객체들은 클래스의 작동을 모방하여 클래스들이 테스트를 받을 수 있도록 한다. mock 객체들의 수는 애플리케이션 클래스의 수만큼 증가할 수 있다. jMock, RMock, EasyMock은 물리적이고 개별적으로 존재하는 mock 객체들에 대한 필요성을 줄인다.

EasyMock 프레임웍의 큰 단점 중 하나는 구체적인 클래스를 모방할 수 없다는 점이다. — 오직 인터페이스만 가능하다. 이 글에서, jMock 프레임웍을 사용하여 구체적인 클래스 인터페이스를 모방하는 방법과 RMock으로 특정 모호한 케이스들을 테스트 하는 방법을 설명하겠다.


Eclipse 플랫폼은 jMock과 RMock 테스팅 프레임웍을 쉽게 사용할 수 있는 메커니즘을 제공합니다.

Eclipse IDE에 jMock과 RMock 설정하기

주: JUnit, jMock, RMock의 최신 바이너리는 참고자료 섹션을 참조하라.

Eclipse 통합 개발 환경(IDE)를 시작한다. JUnit, jMock, RMock Java Archive (JAR) 라이브러리를 가져올 기본 자바™ 프로젝트를 만든다. 자바 프로젝트의 이름을 TestingExample로 한다. 자바 퍼스펙티브에서, Project > Properties를 선택하고 Libraries 탭을 클릭한다.


그림 1. Eclipse의 TestingExample 프로젝트용 편집 프로퍼티
Eclipse의 TestingExample 프로젝트용 편집 프로퍼티

JAR 파일들이 Java classpath (다시 말해서, Eclipse 내에 설정했던 Java Runtime Environment (JRE))에 있을 때 Add JARs 버튼을 사용한다. Add Variable 버튼은 파일 시스템(로컬 또는 원격)에 있는 (JAR를 포함한)리소스들이 상주할 특정 디렉토리에 적용되고 일반적으로 참조될 수 있다. Eclipse에서 기본이고 특정 Eclipse 워크스페이스 환경을 위해 설정된 특정 리소스를 참조해야 할 때 Add Library 버튼을 사용한다. Add Class Folder를 클릭하여 프로젝트의 일부로서 이미 설정된 기존 프로젝트 폴더들 중 하나에서 리소스를 추가한다.

예를 들어, Add External JARs를 클릭하고, 다운로드 했던 jMock과 RMock JAR를 검색한다. 이들을 프로젝트에 추가한다. 그림 2와 같은 프로퍼티 창이 나타나면 OK를 클릭한다.


그림 2. TestingExample Project에 추가된 jMock과 RMock JAR
TestingExample Project에 추가된 jMock과 RMock JAR




위로


TestExample 소스 코드

TestExample Project의 경우, 네 개의 클래스에서 소스 코드로 작업해야 한다.

  • ServiceClass.java
  • Collaborator.java
  • ICollaborator.java
  • ServiceClassTest.java

테스트 시 클래스는 ServiceClass가 될 것이다. 여기에는 하나의 메소드 runService()가 포함된다. 이 서비스 메소드는 Collaborator라고 하는 객체를 취하는데, 이는 인터페이스인 ICollaborator를 구현한다. 하나의 메소드가 구체적인 Collaborator 클래스인 executeJob()에 구현된다. Collaborator는 여러분이 올바르게 모방해야 하는 클래스이다.

네 번째 클래스는 테스트 클래스, ServiceClassTest이다. (구현은 가능한 단순하게 유지한다.) Listing 1은 네 번째 클래스에 대한 코드 모습이다.


Listing 1. 서비스 클래스 샘플 코드
                
public class ServiceClass {
public ServiceClass(){
//no-args constructor
}

public boolean runService(ICollaborator collaborator){
if("success".equals(collaborator.executeJob())){
return true;
}
else
{
return false;
}
}
}

ServiceClass 클래스에서, if...else 코드 블록은 경로가 테스트 기대값에 따라 취해질 경우 실패 또는 성공했는지의 이유를 보여주는 단순한 논리적 교차점이다. Collaborator 클래스용 소스 코드는 다음과 같다.


Listing 2. Collaborator 클래스 샘플 코드
                
public class Collaborator implements ICollaborator{
public Collaborator(){
//no-args constructor
}
public String executeJob(){
return "success";
}

}

Collaborator 클래스 역시 단순하다. 인자 생성자가 없고, executeJob() 메소드에서 리턴된 단순한 String이 있을 뿐이다. 아래 코드는 ICollaborator 클래스용 코드를 보여주고 있다.

public interface ICollaborator {
public abstract String executeJob();
}

ICollaborator 인터페이스는 Collaborator 클래스에서 구현되어야 하는 단일 메소드를 갖고 있다.

이제 다른 시나리오에서 ServiceClass 클래스를 성공적으로 테스트 하는 방법에 대해 알아보자.




위로


시나리오 1: jMock을 사용하여 인터페이스 모방하기

ServiceClass 클래스에서 서비스 메소드를 테스트 하는 것은 간단하다. 테스트 요구 사항이 runService() 메소드가 실행되지 않았다는 것을 선언하는 것이라면, 다시 말해서, Boolean 결과가 false일 경우를 생각해보자. 이와 같은 경우에, runService() 메소드로 전달된 ICollaborator 객체는 이것의 메소드인 executeJob()에 대한 호출이 기다리고 "success"외 다른 스트링을 리턴하는 것으로 모방된다. 이러한 방식으로, Boolean 스트링 false가 테스트로 리턴되는 것을 볼 수 있다.

ServiceClassTest 클래스용 코드에는 테스트 로직이 포함되어 있다.


Listing 3. 시나리오 1에 대한 ServiceClassTest 클래스 샘플 코드
                
import org.jmock.Mock;
import org.jmock.cglib.MockObjectTestCase;
public class ServiceClassTest extends MockObjectTestCase {
private ServiceClass serviceClass;
private Mock mockCollaborator;
private ICollaborator collaborator;

public void setUp(){
serviceClass = new ServiceClass();
mockCollaborator = new Mock(ICollaborator.class);
}

public void testRunServiceAndReturnFalse(){
mockCollaborator.expects(once()).method\
("executeJob").will(returnValue("failure"));
collaborator = (ICollaborator)mockCollaborator.proxy();
boolean result = serviceClass.runService(collaborator);
assertFalse(result);
}
}

테스트를 작성할 때

테 스트 mock 프레임웍에서 실행하는 최상의 방법은 테스트 우선의 신속한 방식을 사용하는 것이다. 테스트를 먼저 만들고 기대값을 설정한다. 테스트가 실패한 후에야, 테스트를 수정 할 구현을 작성한다. 테스트가 작동하면, 또 다른 테스트를 작성하여 나중에 테스트 시 클래스에 추가한 기능을 검사한다.

다양한 테스트 케이스들에 일반 연산들이 수행되어야 한다면 테스트에 setUp() 메소드를 포함시키는 것도 좋은 생각이다. tearDown() 메소드 역시 좋은 생각이지만, 통합 테스트를 수행하고 있는 것이 아니라면 꼭 그럴 필요가 없다.

jMock과 RMock을 사용할 때, 프레임웍은 테스트 실행 끝과 중간에 모든 mock 객체들에 대한 기대값을 검사한다. 각 mock의 기대값에 verify() 메소드를 포함시킬 필요는 없다. JUnit 테스트로서 실행할 때, 테스트는 성공한다.


그림 3. 시나리오1 테스트 통과
시나리오1 테스트 통과

ServiceTestClass 클래스는 jMock CGLIB의 org.jmock.cglib.MockObjectTestCase 클래스를 확장한 것이다. mockCollaborator는 간단한 org.jmock.JMock 클래스이다. 일반적으로, jMock으로 mock 객체들을 만드는 두 가지 방법이 있다.

  • 인터페이스를 모방하려면 new Mock(Class.class) 메소드를 사용한다.
  • 구체적 클래스를 모방하려면, mock(Class.class, "identifier") 메소드를 사용한다.

ServiceClass 클래스의 runService() 메소드로 mock proxy가 전달되는 방식을 알아두는 것이 중요하다. jMock을 사용하여, 생성된 mock 객체들에서 프록시 구현들을 추출할 수 있다. 여기에서 기대값이 이미 설정되었다. 이 부분은 다음 시나리오에서 매우 중요한 것이다. RMock이 관여할 경우 특히 그렇다.




위로


시나리오 2: jMock을 사용하여 기본 생 성자를 가진 구체적 클래스 모방하기

ServiceClassrunService() 메소드가 Collaborator Collaborator 클래스의 구체 구현만 수락한다고 가정해 보자. jMock은 기대값을 변경하지 않고 이전의 테스트를 통과시킬 정도로 충분한가? 간단하고 기본적인 방식으로 Collaborator 클래스를 구현할 수 있는 한 그렇다.

ServiceClass 클래스의 runService() 메소드를 수정하여 아래 코드를 반영해 보자.


Listing 4. 시나리오 2를 위해 편집된 ServiceClass 클래스
                
public class ServiceClass {
public ServiceClass(){
//no-args constructor
}

public boolean runService(Collaborator collaborator){
if("success".equals(collaborator.executeJob())){
return true;
}
else{
return false;
}
}
}

ServiceClass 클래스의 if...else 로직 브랜치는 같다. 또한, 무 인자(no-arguments) 생성자 역시 적소에 있다. while...do 문이나 for 루프 같은 로직이 클래스의 메소드를 테스트하게 할 필요는 없다. 클래스가 사용하는 객체에 대한 메소드 실행이 있는 한, 간단한 mock 기대치로 그러한 실행들을 테스트할 수 있다.

ServiceClassTest 클래스를 수정하여 시나리오에 맞출 수 있다.


Listing 5. 시나리오 2를 위해 편집된 ServiceClassTest 클래스
                
...
private ServiceClass serviceClass;
private Mock mockCollaborator;
private Collaborator collaborator;

public void setUp(){
serviceClass = new ServiceClass();
mockCollaborator = mock(Collaborator.class, "mockCollaborator");
}

public void testRunServiceAndReturnFalse(){
mockCollaborator.expects(once()).method("executeJob").will(returnValue("failure"));
collaborator = (Collaborator)mockCollaborator.proxy();
boolean result = serviceClass.runService(collaborator);
assertFalse(result);
}
}

여기에서 주목해야 할 몇 가지 포인트가 있다. 우선, runService() 메소드 서명은 이전에 비하여 바뀌었다. ICollaborator 인터페이스를 수락하는 대신, 이제는, 구체적 클래스 구현(Collaborator 클래스)를 수락한다. 이러한 변화는 테스팅 프레임웍이 관여되어 있는 한 중요하다. (비 다형성이라는 특징이 있지만, 예제를 위해서 구체적인 클래스를 전달하는 예제를 사용한다. 실제 객체 지향 방식으로는 수행되어서는 안된다.)

두 번째, Collaborator 클래스를 모방하는 방식이 바뀌었다. jMock CGLIB 라이브러리는 구체적인 클래스 구현을 모방할 수 있다. jMock CGLIB의 mock() 메소드에 제공된 여분의 String 매개변수는 생성된 mock() 객체용 식별자로서 사용된다. jMock(그리고 실제로 RMock)을 사용할 때, 하나의 테스트 케이스 내에 mock 객체 설정마다 고유 식별자가 필요하다. 이는 일반 setUp() 메소드 또는 실제 테스트 메소드에서 정의된 mock 객체들도 마찬가지다.

세 번째로, 테스트 메소드의 원래 기대값은 바뀌지 않았다. 테스트를 통과시키기 위해서 false 선언이 여전히 필요하다. 일정한 테스트 결과를 허용하면서, 다른 인풋에 대한 변화를 수용할 정도로 테스팅 프레임웍이 유연하다는 것을 보여줌으로써, 같은 결과를 만들어 내기 위해 인풋이 수용될 수 없을 때 진정한 한계가 나타나기 때문에 중요하다.

이제, JUnit 테스트로서 테스트를 재실행 한다. 테스트는 아래와 같이 통과한다.


그림 4. 시나리오 2 테스트 통과
시나리오 2 테스트 통과

다음 시나리오에서, 상황은 약간 더 복잡해졌다. RMock 프레임웍을 사용하여 어려운 상황처럼 보이는 것을 제거한다.




위로


시나리오 3: jMock과 RMock을 사용하여 특정 생성자를 가진 구체적 클래스 모방하기

전처럼, jMock을 사용하여 Collaborator 객체를 모방하는 것으로 시작한다. Collaborator는 기본적인 무 인자 생성자를 갖고 있지 않다. 불린 false 결과의 테스트 기대값이 유지된다.

또한, Collaborator 객체는 생성자로 전달된 매개변수로서 하나의 스트링과 프리머티브 int를 필요로 한다. Listing 6은 Collaborator 객체에 생긴 변화이다.


Listing 6. 시나리오 3에서 변경된 Collaborator 클래스
                
public class Collaborator{
private String collaboratorString;
private int collaboratorInt;

public Collaborator(String string, int number){
collaboratorString = string;
collaboratorInt = number;
}
public String executeJob(){
return "success";
}
}

Collaborator 클래스 생성자는 여전히 간단하다. 클래스 필드는 인커밍 매개변수로 설정된다. 다른 로직은 여기에서는 필요하지 않고, executeJob() 함수도 같다.

예제의 다른 모든 컴포넌트로 테스트를 재실행 한다. 결과는 끔찍한 테스트 오류이다.


그림 5. 시나리오 3 테스트 오류
시나리오 3 테스트 오류

위 테스트는 코드 커버리지 없이 간단한 JUnit 테스트로서 실행되었다. 코드 커버리지 툴(Cobertura 또는 EclEmma) 을 사용하여 이 글에 리스팅 된 테스트를 실행할 수 있다. 하지만, Eclipse에서 코드 커버리지로 RMock 테스트를 실행할 때 몇 가지 문제가 있다. (표 1) 아래 코드는 실제 스택 트레이스 모습이다.


Listing 7. 시나리오 3에서 테스트 오류에 대한 스택 트레이스
                
...Superclass has no null constructors but no arguments were given
at net.sf.cglib.proxy.Enhancer.emitConstructors(Enhancer.java:718)
at net.sf.cglib.proxy.Enhancer.generateClass(Enhancer.java:499)
at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216)
at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)
at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:285)
at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:660)
.....
.....

오류 이유는 jMock이 무 인자 생성자가 없는 클래스 정의에서 가능한 mock 객체를 만들 수 없기 때문이다. Collaborator 객체를 인스턴스화 하는 유일한 방법은 두 개의 간단한 인자를 제공하는 것이다. 여러분은 이제 mock 객체 인스턴스화 과정에 대한 인자가 같은 효과를 내는 방법을 찾아야 한다. 바로 이것이 RMock을 사용하는 이유이다.

RMock 테스팅 프레임웍에서 실패한 테스트 정정하기

테스트를 정정하려면, 몇 가지 수정이 필요하다. 미묘한 것처럼 보이지만, 실제로는 두 프레임웍들의 힘을 활용하는 간단한 해결책이다.

첫 번째 필요한 변화는 테스트 클래스를 jMock CGLIB TestCase가 아닌 RMock TestCase로 만드는 것이다. 이렇게 하는 이유는 테스트 내의 RMock에 속한 mock 객체들을 초기 설정 시 더욱 쉽게 설정하기 위함이다. 테스트 클래스의 확장 기반인 TestCase 객체가 RMock에 속해있다면 두 프레임웍들에서 mock 객체들을 구현 및 사용하는 것이 더 쉽다. 더욱이, mock 객체들의 흐름을 빠르게 결정하기에도 더 쉽다. (여기에서 플로우는 mock 객체를 매개변수로서 사용하고, 다른 mock 객체들의 리턴 유형으로서 사용하는 상황을 설명하기 위해 사용된다.)

두 번째 변화는 Collaborator 클래스의 생성자로 전달된 실제 매개변수의 값을 보유하고 있는 객체 어레이를 구현하는 것이다. 생성자가 수락하는 클래스 유형 어레이를 포함시키고, 매개변수로서 기술된 객체 어레이 mock Collaborator 객체를 인스턴스화 하기 위해 그 어레이를 전달하는 것도 가능하다.

세 번째 변화는 정확한 신택스를 가진 RMock mock 객체에 대한 한 개 이상의 예외를 구현하는 것이다. 네 번째와 마지막 변화는 기록 상태의 RMock mock 객체를 준비 상태로 가져오는 것이다.

RMock의 변경 내용을 구현하기

Listing 9는 ServiceClassTest 클래스에 대한 마지막 수정 모습이다. RMock과 관련 기능의 도입에 대해 나타내고 있다.


Listing 9. 시나리오 3에서 ServiceClassTest 수정하기
                
...
import com.agical.rmock.extension.JUnit.RMockTestCase;
public class ServiceClassTest extends RMockTestCase {

private ServiceClass serviceClass;
private Collaborator collaborator;

public void setUp(){
serviceClass = new ServiceClass();
Object[] objectArray = new Object[]{"exampleString", 5};
collaborator =
(Collaborator)intercept(Collaborator.class, objectArray, "mockCollaborator");
}

public void testRunServiceAndReturnFalse(){
collaborator.executeJob();
modify().returnValue("failure");
startVerification();
boolean result = serviceClass.runService(collaborator);
assertFalse(result);
}
}

먼저, 테스트의 기대값은 여전히 변하지 않았다. RMockTestCase 클래스의 반입은 RMock 프레임웍 기능의 도입을 나타낸다. 그 다음, 테스트 클래스는 이제, MockObjectTestCase 보다는 RMockTestCase를 확장한다. 나중에, TestClass 객체가 여전히 RMockTestCase 객체의 유형인 테스트 케이스 내의 MockObjectTestCase의 재도입에 대해 설명하겠다.

intercept() 메소드에 대한 대안

RMock의 경우, intercept() 메소드를 사용해서는 구체적인 클래스만 모방할 수 있다. RMock mock() 메소드를 사용하여 구체적인 클래스와 인터페이스를 모방할 수 있다. 소수의 메소드만 모방해야 할 때, interface() 메소드를 사용하라. 이 메소드를 mock() 메소드로 간주하라.

setUp() 메소드 내에서, Collaborator 클래스의 생성자가 필요로 하는 실제 값으로 객체 어레이를 인스턴스화 한다. 이 어레이는 RMock의 intercept() 메소드로 들어가서 mock 객체의 인스턴스화를 돕는다. 메소드의 서명은 jMock CGLIB mock() 메소드의 서명과 비슷하다. 두 메소드 모두 인자로서 고유 mock 객체 식별자를 취하기 때문이다. Collaborator 유형으로의 mock 객체의 클래스 캐스트는 필요하다. intercept() 메소드가 Object 유형을 리턴하기 때문이다.

테스트 메소드, testRunServiceAndReturnFalse()에서, 몇 가지 변화를 볼 수 있다. mock Collaborator 객체의 executeJob() 메소드가 호출된다. 이 단계에서, mock 객체는 기록 상태(record state)에 있다. 다시 말해서, 기대하는 메소드 호출을 정의한다. 따라서, mock은 이에 따른 기대값을 기록한다. 다음 라인은 mock 객체에 대한 공지로서, 이것인 executeJob() 메소드를 만날 때, failure 스트링을 리턴 하도록 한다. 따라서, RMock에서는 mock 객체에서 메소드를 호출함으로써(그리고 필요한 매개변수를 전달하여) 기대 값을 나타내고, 이에 따라 기대값을 수정하여 리턴 유형을 조정한다.

마지막으로, RMock 메소드 startVerification()은 mock Collaborator 객체를 준비 상태(ready state)로 만들기 위해 호출된다. 이 mock 객체는 실제 객체로서 ServiceClass 클래스에서 사용할 준비가 된다. 이 메소드는 매우 필수적인 것이며 테스트 초기화 오류를 피하기 위해 호출되어야 한다.

변경 사항 테스트 하기

ServiceClassTest를 다시 한번 재실행 하여 마지막으로 긍정적인 결과를 이룩한다. 여러분이 mock 객체 인스턴스화 과정 동안 제공했던 매개변수들이 이 모든 차이를 만든다. 그림 6은 성공을 나타내는 연두색을 보여주고 있다.


그림 6. RMock에서 시나리오 3의 테스트 성공
RMock에서 시나리오 3의 테스트 성공

assertFalse(result) 코드 라인은 시나리오 1의 같은 테스트 기대값을 나타내고, RMock은 jMock이 앞서 이룩했던 테스트 성공을 이루었다. 여러 가지 면에서 중요하지만, 가장 중요한 것은 테스트 기대값을 바꾸지 않고 실패한 테스트를 수정하는 애자일(agile) 원리가 적용되었다는 점이다. 유일한 차이는 대안 프레임웍이 사용되었다는 점이다.

다음 시나리오에서는, jMock과 RMock을 특별한 케이스에 사용할 것이다. 테스트 내의 동맹이 형성되지 않는 한 올바른 결과를 만들어 낼 수 없다.




위로


시나리오 4: jMock과 RMock의 특별한 협업

앞 서 언급했듯이, 두 개의 프레임웍들이 협력하여 특정 결과를 만들어 내는 케이스를 연구해야 했다. 그렇지 않았다면, 잘 만들어진 테스트도 매번 실패했을 것이다. jMock을 사용하든 RMock을 사용하든 문제가 되지 않는 상황이 있다. 이러한 상황에서는, 모방하고자 하는 인터페이스나 클래스가 서명이 되어 있는 JAR에 존재한다. 하지만 이 같은 상황은 극히 드물고, 테스팅 코드가 안전한 상용 제품들의 애플리케이션 프로그래밍 인터페이스(API)에 작성되었을 때 발생한다.

Listing 10은 두 프레임웍이 테스트 케이스를 실행하는 예제를 보여주고 있다.


Listing 10. 시나리오 4의 테스트 예제
                
public class MyNewClassTest extends RMockTestCase{

private MyNewClass myClass;
private MockObjectTestCase testCase;
private Collaborator collaborator;
private Mock mockClassB;

public void setUp(){
myClass = new MyNewClass();

testCase = new MyMockObjectTestCase();

mockClassB = testCase.mock(ClassB.class, "mockClassB");
mockClassB.expects(testCase.once()).method("wierdMethod").
will(testCase.returnValue("passed"));

Class[] someClassArray = new Class[]{String.class, ClassA.class, ClassB.class};
Object[] someObjectArray = new Object[]
{"someArbitraryString", new ClassA(), (ClassB)mockClassB.proxy()};

collaborator = (Collaborator)intercept
(Collaborator.class, someClassArray, someObjectArray, "mockCollaborator");
}

public void testRMockAndJMockInCollaboration(){
startVerification();
assertTrue(myClass.executeJob(collaborator));
}

private class MyMockObjectTestCase extends MockObjectTestCase{}

private class MyNewClass{
public boolean executeJob(Collaborator collaborator){
collaborator.executeSomeImportantFunction();
return true;
}
}
}

setUp() 메소드에서, 새로운 "testcase"가 jMock-CGLIB MockObjectTestCase 객체를 확장하기 위해 생성된 프라이빗 내부 클래스에 기반하여 인스턴스화 된다. 이러한 작은 해결책은 전체 테스트 클래스를 RMock TestCase 객체로 유지하면서, jMock 기능을 사용하는데 필수적이다. 예를 들어, once()가 아닌 testCase.once() 같은 jMock 예외를 설정할 수 있다. TestClass 객체는 RMockTestCase를 확장하기 때문이다.

ClassB 클래스에 기반한 mock 객체가 생성되고 예외가 만들어진다. 이것을 사용하여 RMock Collaborator mock 객체를 인스턴스화 하는데 사용한다. 테스트 중인 클래스는 MyNewClass 클래스이다. executeJob() 메소드는 Collaborator 객체를 받고 executeSomeImportantFunction() 메소드를 실행한다.

Listing 11과 12는 ClassAClassB용 코드를 보여주고 있다. ClassA는 구현이 없는 단순한 클래스이고, ClassB는 그 포인트를 설명하기 위해 최소한의 상세만 나타낸다.


Listing 11. ClassA 클래스
                
public class ClassA{}

이 클래스는 단순한 더미 클래스로서, 생성자가 객체 매개변수를 받는 mock 클래스에 RMock이 필수적이라는 것을 강조한다.


Listing 12. ClassB 클래스
                
public class ClassB{
public ClassB(){}
public String wierdMethod(){
return "failed";
}

}

ClassB 클래스의 wierdMethodfailed를 리턴한다. 클래스 성공을 나타내는 또 다른 스트링을 리턴 해야 하기 때문에 중요하다.

Listing 13은 이 테스트 예제에서 가장 중요한 부분이다. 바로 Collaborator 클래스이다.


Listing 13. Collaborator 클래스
                
public class Collaborator {
private String _string;
private ClassA _classA;
private ClassB _classB;

public Collaborator(String string, ClassA classA, ClassB classB) throws Exception{
_string = string;
_classA = classA;
if(classB.wierdMethod().equals("passed")){
_classB =classB;
}
else{
throw new Exception("Something bad happened");
}
}

public void executeSomeImportantFunction(){
}
}

가장 먼저 알아두어야 할 것은 jMock을 사용하여 ClassB 클래스를 모방했다는 것이다. RMock에서는, mock 객체에서 프록시를 추출 및 사용하여 이것을 테스트 setUp() 메소드에서 사용할 방법이 없다. RMock에서는, 프록시 객체는 startVerification() 메소드가 호출된 후에만 나타난다. 이 경우는 jMock에 알맞다. 그 자체로 mock인 객체들을 리턴해야 하는 상황에서 다른 mock 객체들에 대해 설정해야 하는 것을 얻을 수 있기 때문이다.

두 번째로 중요한 것은, jMock 프레임웍을 사용하여 Collaborator 클래스를 모방할 수 없다는 것이다. 이 클래스는 무 인자 생성자를 갖고 있지 않기 때문이다. 더욱이, 인스턴스가 처음 획득될 수 있는지 여부를 결정하는 생성자 내에 특정 로직이 있다. 사실, ClassBwierdMethod() 메소드는 Collaborator 객체가 인스턴스화 되도록 passed를 리턴해야 한다. 하지만, 기본적으로, 이 메소드는 언제나 failed를 리턴한다. 테스트를 성공하려면 ClassB를 모방해야 할 필요가 있다.

또한, 이전 예제와는 달리, 이 시나리오의 클래스 어레이는 intercept() 메소드에 대한 여분의 매개변수로서 포함되었다. 그렇게 필요한 것은 아니지만, RMock 테스트 객체를 인스턴스화 할 때 여러분이 사용했던 객체 클래스가 어떤 것인지를 빠르게 구분해 낸다.

새로운 테스트 케이스를 실행하라. 이번에는 성공적인 결과를 볼 수 있다. 그림 7은 해피 엔딩을 나타낸다.


그림 7. RMock과 jMock의 협업을 통한 시나리오 4의 테스트 성공
RMock과 jMock의 협업을 통한 시나리오 4의 테스트 성공

Collaborator mock 객체가 올바르게 설정되고 mockClassB 객체가 기대한 대로 작동한다.




위로


테스트 툴 차이점

시 나리오들을 통해 보았겠지만, jMock과 RMock은 자바 코드를 테스트 하는 강력한 툴이다. 하지만, 개발과 테스팅에 사용되는 다른 툴에는 한계가 있다. 실제로, 다른 테스팅 툴들을 사용할 수 있지만, 어떤 것도 (자바에서는) RMock과 jMock을 따라가지 못한다. Microsoft® .NET 프레임웍도 강력한 툴 역할을 하지만(TypeMock), 이 글의 범위를 벗어난다.

표 1에는 Eclipse 환경을 기반으로 한 두 가지 프레임웍의 차이점과 문제를 비교해 놓았다.


표 1. RMock과 jMock 테스팅 프레임웍의 차이점
테스트 스타일jMock RMock
인터페이스 모방Yes: 새로운 Mock() 메소드Yes: mock() 메소드
구체적 클래스 모방Yes: mock() 메소드와 CGLIBYes: mock() 또는 intercept() 메소드
모든 구체적 클래스 모방No: 무 인자(no-argument) 생성자가 있어야 한다.Yes
프록시 획득 여부YesNo: startVerification() 준비 상태 후에만 가능.
기타 Eclipse 플러그인과 관련한 문제들없음Yes: Eclipse용 CoverClipse 플러그인과 충돌(in-memory)



위로


요약

단 위 테스팅에 이러한 프레임웍들을 사용해 보기 바란다. 많은 자바 개발자들은 테스트를 자주 작성하지 않는다. 이들이 테스트를 작성한다면, 메소드의 주요한 기능적인 측면만을 다루는 매우 단순한 것들이다. 코드에서 다루기 힘든 부분을 테스트 하려면 jMock과 RMock이 제격이다.

이들을 사용하면 코드에 버그를 현격히 줄일 수 있고, 입증된 방식을 사용하여 프로그래밍 로직을 테스트할 때 스킬을 향상시킬 수 있다. 또한, 문서를 읽고 이러한 프레임웍을 직접 사용하면 개발자로서의 스킬 향상에 도움이 된다.



참고자료

교육

제품 및 기술 얻기

토론


필자소개

Michael Nyika

Michael Nyika는 북부 버지니아 지역을 중심으로 활동하는 J2EE 컨설턴트이다. 7년 동안 자바와 Microsoft .NET 플랫폼을 기반으로 소프트웨어를 개발했으며 Linux/SELinux 옹호론자이다.