메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

한빛랩스 - 지식에 가능성을 머지하다 / 강의 콘텐츠 무료로 수강하시고 피드백을 남겨주세요. ▶︎

IT/모바일

Flex 개발자를 위한 자동화된 테스팅 매뉴얼 : Part 2

한빛미디어

|

2008-12-17

|

by HANBIT

12,624

제공 : 한빛 네트워크
저자 : Michael Labriola
역자 : 오윤재
원문 : Automated Testing and You, Self-Help for the Flex Developer: Part 2

이전 Part 1 기사에서, 우리는 Flunit를 이용해 어떻게 몇몇 기초적이고 간단한 단위 테스트(unit test)들을 만들 수 있는가를 보았다. 이것들은 매우 기본적인 상황에서는 잘 적용된다. 하지만 일반적인 Flex 애플리케이션 조차도 이벤트들을 사용하고, 비동기적인 면들을 가지고 있다. 이번 기사에서는 그 비동적인 상황을 설정하고, 이런 개념을 염두에 둔 가운데 시스템을 어떻게 테스트할 수 있는지를 보여줄 것이다. (어떤 상황이) 계속되기 전 발생할 이벤트를 위해 대기하는 단위 테스트(unit test)들과 그것의 성공과 실패가 미래 언젠가 발생할 무언가에 달려있는 테스트를 작성하는 방법도 배우게 될 것이다.

다음 기사에서는, 이전의 단위 테스트(unit test)들로 돌아가 통합 테스팅(integration testing)의 개념을 살펴볼 예정이다. 거기서는 기존 단위 테스팅과 달리 더 이상 가장 작은 부분들을 테스트하기 보다는 함께 결합되어 동작해야할 단위들 모음을 테스트할 것이다. test 절차(sequence)들을 개발하기 위한 새로운 syntax를 포함해 비동기(asynchronous)와 이벤트 기반의 test에 대한 이해를 바탕으로, 여러분은 사용자 인터페이스 컴포넌트(UIComponent)들과 더 규모가 있는 상황의 Flex 또는 AIR 애플리케이션을 위한 빠르고 효율적인 test를 작성할 수 있게 될 것이다.

전제조건

Adobe Flex Builder 3
Flex와 ActionScript에 대한 기본 지식
“Flex 개발자를 위한 자동화된 테스팅 매뉴얼 : Part 1” 읽기

이벤트 기반의 동기(Synchronous) 및 비동기(Asynchronous) test 이해하기

이전 기사에서 여러분이 작성한 테스트는 간단한 동기(synchronous) test였다. absoluteValue() 메서드를 호출하면, 그것은 그 결과를 즉각 돌려주고, 여러분은 그 결과를 확인했다.

불행하게도, 세상은 그렇게 간단하지만은 않다. 난 샌드위치를 매우 좋아한다. 그래서 우리 동네 식당을 예로 들겠다. 프로세스는 매우 간단하다. ‘계산대 직원한테 다가간다’. ‘그녀(혹은 그가) 여러분의 이름과 여러분이 원하는 것을 물어본다’. ‘여러분의 샌드위치가 준비되면, 여러분의 이름이 호명된다’. ‘여러분은 여러분의 이름을 듣는다’. ‘그들은 적당한 시간이 되었을 때, 여러분의 이름을 부른다’. 이것이 실세계 이벤트 기반 시스템의 한가지 예이다.

하지만 다른 상황들에 따라 이런 프로세스의 간격에는 약간의 불일치가 생긴다. 만일 식당이 매우 분비면, 그 계산원은 여러분의 주문을 그 샌드위치를 만들 다른 사람에게 전달한다. 그런 다음, 그 계산원이 여러분의 주문에 대해 청구한다. 향후 어떤 시점, 즉, 그 샌드위치를 만드는 사람이 다 마쳤을때, 그 계산원은 여러분의 이름을 부른다. 여러분은 그 샌드위치를 받아든다. 그리고, 그것을 먹는다.

만일 식당이 분비지 않으면, 여러분이 아직 여러분의 이름을 대고 주문하고 있는데, 그 계산원은 즉각 샌드위치 만들기를 시작한다. 다 준비되면, 그녀가 여러분의 이름을 부르고, 금액을 청구하다. 그런 다음, 여러분은 그것을 먹는다. (이것은 여러분의 마지막 말이 떨어지기 전까지 아무일도 안하고 기다리다가 그제서야 여러분의 샌드위치를 만들기 시작하는 것처럼 우수꽝스러운 일이다.)

첫번째 예가 비동기(asynchronous) 이벤트 기반 모델이다. 여러분이 주문을 하고, 이러저래 놀다가 (여러분의 이름이 불려질) 그 이벤트가 발생할 언젠지 모르는 시간 동안 기다린다. 두번째 예 또한 여전히 이벤트 기반의 모델이다. 하지만 그건 동기적으로(synchronously) 발생한다. 여러분이 주문한다. 그렇지만 그 샌드위치 준비 이벤트는 그 프로세스가 계속되기 전 발생한다. 종합적인 결과는 여러분이 주문한 샌드위치를 받았을 때와 그와 관련해 지불할 때 차이가 난다.

Flex와 AIR의 세계에선 큰 차이가 없다. 아래 첫번째 예는 실제 비동기(asynchronous) 이벤트 기반 상황을 보여준다.
function handleEvent( event:Event ):void {
 	trace("Handling Event");
}

var blah:HTTPService();
blah.addEventListener( ResultEvent.RESULT, handleEvent );
blah.send();
trace("End of code block");
이 코드가 동작했을때, "End of code block" 라는 trace 구문이 콘솔에 보여진다. 그런 다음, 향후 어떤 시점에 “Handling Event” 메서드가 발생한다.

아래의 예는 동기(synchronous) 이벤트 기반의 상황을 보여준다.
function handleEvent( event:Event ):void {
 	trace("Handling Event");
}
		
var blah:TextInput = new TextInput();
addChild( blah );
blah.addEventListener( FlexEvent.VALUE_COMMIT, handleEvent );
blah.text = "Mike";
trace("End of code block");
이번 예제에서는, “Handling Event”라는 trace 구문이 “End of code block” 구문 이전에 발생할 것이다. 왜냐하면 향후 어떤 시점에서가 아니라 텍스트가 변경되자 마자 텍스트 입력 필드에서 valueCommit 이벤트가 전달되기 때문이다. 이 예는 이벤트 기반이긴 하지만 동기(synchronous) 방식이다.

Fluint를 통해 테스트할 때, 여러분은 비동기(asynchronous) 방식으로 모든 이벤트 기반의 상황들을 처리할 것이다. Fluint가 내부적으로 그 차이가 나는 부분들을 처리하고, 테스트가 여전히 정확하게 해결되는지를 확실하게 할 것이다.

첫번째 비동기 테스트 작성하기

대부분의 테스팅 프레임워크는 타이머(timer)들을 사용해 비동기를 처리하는 것을 보여준다. 우리도 이런 기본방식에서 벗어나지 않을 것이다. 왜냐하면 그것은 비동기 이벤트를 쉽게 이해할 수 있는 방법이기 때문이다. 타이머를 사용해 비동기 test를 생성하기 위해 여러분은 setUp() 메서드 안에 타이머를 생성하고, 그것을 test 메서드 안에서 시작한 다음, tearDown() 메서드 안에서 해제시킬 것이다. 이전 기사의 예들과 달리, assertion(검증을 위한 메서드)는 result 핸들러(handler)에서 처리하도록 할 것이다.

test method, case 및 suite 생성하기

1) 이전 기사에서 시작한 프로젝트를 연다.

2) src 폴더 안에 asyncSuite라는 새로운 디렉토리를 생성한다.

3) asyncSuite 폴더 안에 tests 라는 폴더를 생성한다.

4) tests 폴더 안에 net.digitalprimates.fluint.tests.TestCase 를 슈퍼클래스로하는 TestTimer라는 새로운 ActionScript 클래스를 생성한다.

5) 이번 test case 안에서 사용할 두개의 timer 클래스에 대한 import 구문을 추가한다.
import flash.events.TimerEvent;
import flash.utils.Timer;
6) Timer 타입의 timer라는 변수를 protected로 정의한다.
protected var timer:Timer;
7) 그 클래스 안에 setUp() 메서드를 오버라이드(override)하는 protected 메서드를 생성한다. super.setUp() 가 호출된 다음 timer의 새로운 객체를 인스턴스화 하는데, 이것은 1000ms를 대기했다 한번씩 실행할 것이다. timer 객체는 생성될 때 초기화 변수를 필요로 한다. 하지만 이것은 나중에 수정될 것이다.
override protected function setUp():void {
 		super.setUp();
 
 		timer = new Timer( 1000, 1 );
 	}
8) tearDown() 메서드를 오버라이드하는 protected로 되어 있는 또 다른 메서드를 생성한다. super.tearDown()가 호출된 후, timer를 멈추고 timer 값을 null 로 셋팅한다.
override protected function tearDown():void {
 		super.tearDown();
 
 		if ( timer ) {
  			timer.stop();
  		}
 		
 		timer = null;
 	}
9) null을 반환하는 testTimerSingle()라는 또 다른 public 메서드를 생성한다. 일반적으로 test 메서드는 코드를 실행하고 assertion을 책임지고 있다. 만일 assertion이 참이 아니면, 그 테스트 메서드는 실패한다. 네거티브한 관점에서 말하자면, test 메서드가 참이 아닌 assertion을 만들면, 그것은 성공이다. 비동기(asynchronous) test 경우 이것은 심각한 문제를 내포하고 있다. 왜냐하면 그 test는 즉각적으로 성공 또는 실패를 나타내기 보다 어떤 일이 일어날 결과를 기다려야하기 때문이다.

이 문제는 AsyncHandler라는 클래스를 사용해 해결할 수 있다. AsyncHandler의 인스턴스는 test가 완전히 끝나 성공 혹은 실패를 결정하기전 무엇인가 발생하기를(또는 시간이 경과하기를) 기다린다.

10) testTimerSingle 메서드 안에서, Function 타입의 handler라는 변수를 생성하고, TestCase클래스의 asyncHandler()를 호출한 그 결과값을 거기에 할당한다.
var handler:Function = asyncHandler( handleSingleResult, 2000, 
null, handleShouldNotFail );
asyncHandler() 메서드는 인자로 네개의 값을 받는다. 첫번째는 의도한 이벤트가 어떤 시간적인 방식으로 발생할지를 알려주기 위한 방식이다. 두번째는 이 test가 끝났다고 선언하기 전 그 이벤트를 얼마 동안 기다릴 것인지를 알려준다. 세번째는 test와 함께 전달될 임의의 데이타이다. 마지막으로, 네번째는 시간이 완료되기 전에 그 의도된 결과를 받을지 말지를 알려주기 위한 방식이다.

11) 다음으로는, timer 객체의 delay 속성에 1000을 할당한다. 이것은 여러분이 1초 후 어떤 이벤트를 알려주기 원한다는 것을 나타내는 것이다.

12) timer에 TIMER_COMPLETE 이벤트를 감지할 이벤트 listener를 추가한다. 이 이벤트가 발생할 때, 10)번째 단계에서 생성한 핸들러(handler)를 여러분은 호출하고 싶다. 여러분이 weak reference(역자 주. 가비지 컬렉터에 의해 소멸되도 상관없는 참조형태)를 사용하기 원한다는 것을 알려주기 위해 addEventListener()의 남은 세 가지 인자 값으로 false, 0 그리고 true를 전달하다.
timer.addEventListener(TimerEvent.TIMER_COMPLETE, handler,false, 0, true ); 
13) timer의 start() 메서드를 호출한다. 최종적으로 test 메서드는 아래와 같다.
public function testTimerSingle() : void {
    var handler:Function = asyncHandler( handleSingleResult, 2000, null, 
                                         handleShouldNotFail );
 
    timer.delay = 1000;
    timer.addEventListener(TimerEvent.TIMER_COMPLETE, handler, false, 0, true ); 
    timer.start();
}
14) 두개의 값을 받아들이는 handleSingleResult()라는 새로운 메서드를 생성한다. 첫번째는 event라는 Event 타입이다. 두번째는 passThroughData라는 Object타입이다. 이 메서드는 시간이 다 되기전 timer 이벤트가 발생하면 호출될 것이다.
protected function handleSingleResult( event:Event, passThroughData:Object ):void {
}
이 경우 이벤트 객체는 timer 이벤트이다. passThroughData는 asyncHandler() 메서드를 통해 전달될 세번째 값으로 어떤 데이터도 가질 수 있다. 여기서, 이것은 null값이다.

15) Object 타입의 passThroughData라는 이름의 값을 받는 handleShouldNotFail()라는 새로운 메서드를 생성한다. 다시 한번, 이것도 asyncHandler() 메서드로 전달된 어떤 데이타도 포함할 수있다. 만일 정해진 시간이 다 되기 전에 timer 이벤트가 발생하지 않으면 이 메서드가 호출될 것이다.
protected function handleShouldNotFail( passThroughData:Object ):void {
}
16) handleShouldNotFail() 메서드 안에서, fail() 메서드를 호출한다. fail() 메서드는 테스트 결과로 표시될 메시지를 받는다.
protected function handleSingleResult( event:Event, passThroughData:Object ):void {
 fail("Timeout Reached");
    }
17) 이전에 만든 asyncSuite 디렉토리 안에 net.digitalprimates.fluint.tests.TestSuite를 슈퍼클래스로 하는 AsyncSuite라는 새로운 ActionScript 클래스를 생성한다.

18) 위에서 생성한 test case를 위해 이 클래스의 맨위에 import 구문을 추가한다.
import asyncSuite.tests.TestTimer;
19) AsyncSuite 클래스의 생성자 안에 TestTimer 클래스의 새로운 인스턴스를 추가하기 위해 TestSuite 슈퍼클래스의 addTestCase() 메서드를 사용한다.
public function AsyncSuite() {
 		addTestCase( new TestTimer() );
 	}
AsyncSuite는 현재 하나의 test case를 포함하고 있는데, 이것은 하나씩의 메서드를 가지고 있다. 하지만 실제 테스트 환경에서는 여러분의 suite들은 수십 혹은 수백개의 메서드를 가진 각각의 case를 수백개씩 가질 수도 있다.

20) 테스트를 실행하기 전 마지막 단계는 새로운 test suite를 test runner의 큐에 추가하는 일이다. 이를 위해서 FlexTestRunner.mxml 파일을 열고 startTestProcess() 메서드를 찾는다. 이 메서드는 배열을 생성하고, 그것을 testRunner의 startTests() 메서드에 전달하는 역할을 담당하고 있다.

21) AsyncSuite 클래스의 새로운 인스턴스를 배열에 추가한다.
protected function startTestProcess( event:Event ) : void {
 	var suiteArray:Array = new Array();
 	suiteArray.push( new MathSuite() );
 	suiteArray.push( new AsyncSuite() );
 				
 	testRunner.startTests( suiteArray );
}
만일 이 지점에서 FlexTestRunner.mxml 파일이 실행하면, 세개의 test가 실행되고, 그 세개가 통과된 것을 볼 수 있다. 즉, MathSuite로 부터 두개, 그리고 AsyncSuite로 부터 한개이다.

Pass Through Data 사용하기

PassThroughData는 비동기(asynchronous) test를 더욱 다이나믹하게 만들기 위한 하나의 방법이다. 어떠한 객체 타입도 asyncHandler() 메서드에 전달될 수 있다. 나중에는 그 객체를 성공 또는 실패를 처리하는 핸들러(handler)들에서 이용하는 것도 가능하다. 이러한 개념을 실제 실행해 보기 위해, 새로운 test를 생성할 것이다. 이것은 타이머(timer)를 여러번 반복하고, test가 완료되는 시점에 이것이 정확하게 반복되었는지를 확인하게 해준다.

test 메서드, case 및 suite 생성하기
1) TestTimer 클래스 안에 null값을 리턴하는 testTimerMutliple()라는 새로운 public 메서드를 생성한다.

2) 위의 새로운 메서드의 첫번째 라인에 obj 라는 Object타입의 새로운 변수를 생성하고 그것을 초기화한다.

3) 이 객체에 repeatCount라는 속성을 추가하고, 거기에 4를 할당한다.

4) 다음으로, 여러분이 위에서 작성했듯이 새로운 asyncHandler를 생성한다. 하지만 그것은 이벤트가 성공적일 때 handleMultipleResult() 메서드를 호출하도록 지정한다. 더 나아가, 이 새로운 obj 객체를 asyncHandler() 호출의 passThroughData 속성으로 전달한다.
public function testTimerMultiple() : void {
    	var obj:Object = new Object();
 	obj.repeatCount = 4;
 	var handler:Function = asyncHandler( handleMultipleResult, 2000, obj, 
                                                handleShouldNotFail );
 
}
5) 다음으로 타이머의 delay 속성을 50ms으로 지정한다.

6) 그런 다음 repeatCount 속성에 obj.repeatCount을 지정한다.

7) 위에서 생성한 handler를 참조하는 timer의 이벤트 listener를 추가한다.

8) timer의 시작 메서드를 호출한다.
public function testTimerMultiple() : void {
 var obj:Object = new Object();
 obj.repeatCount = 4;
 
 var handler:Function = asyncHandler( handleMultipleResult, 2000, obj, 
                                      handleShouldNotFail );
 
     	timer.delay = 50;
     	timer.repeatCount = obj.repeatCount;;
 
     	timer.addEventListener(TimerEvent.TIMER_COMPLETE, handler, false, 0, true ); 
     	timer.start();
}
9) 다음으로, event객체와 passThroughData를 인자값으로 받는 handleMultipleResult()라는 새로운 메서드를 생성한다.
protected function handleMultipleResult( event:Event, passThroughData:Object ):void {
}
10) 마지막으로, assertEquals() 메서드를 호출한다. 이 메서드는 timer의 currentCount 속성에 passThroughData의 repeatCount과 같은지를 확인하는 assertion을 만들기 위함이다. 다시 말해, 여러분이 지정한 만큼 그 타이머가 동작했는가를 확인한다.
protected function handleMultipleResult( event:Event, passThroughData:Object ):void {
 	assertEquals( timer.currentCount, passThroughData.repeatCount );
}
만일 여러분이 이 시점에 테스트 실행기(test runner)를 실행하면 네개의 test들이 실행되고, 네개가 성공했다는 결과를 보게 될 것이다. 여러분이 원한다면, 각각의 timer가 실행하는데 걸릴 횟수와 각각의 test가 대기할 시간을 다양하게 해보라. 이것은 실패하게 되는 상황들을 모의로 설정하고, 테스트 실행기(test runner)가 어떻게 반응하는지를 볼 수 있도록 해줄 것이다.

다음 단계들

이번 기사에서는 비동기(asynchronous) 단위 테스트(unit test)들에 대해 소개했다. 앞으로의 기사에서는 통합기능 테스팅(integration and functional testing)에 대해 다룰 것이다. 그동안 여러분은 http://fluint.googlecode.com에서 Fluint의 고급 특징들에 대해 더 공부해 볼 수 있다.
TAG :
댓글 입력
자료실

최근 본 상품0