"
AJAX(Asynchronous JavaScript And XML) 기술을 사용하는 웹 응용 프로그램이 지난 한 해 동안 꾸준히 증가했습니다. 올바르게 작성된 AJAX는 비 AJAX 웹 응용 프로그램에 비해 성능과 사용자 환경 측면에서 월등히 뛰어납니다. 그러나 AJAX 웹 응용 프로그램은 비동기적으로 작동하므로 일반적인 동기식 테스트 자동화 기법을 대부분 적용할 수 없습니다. 이번 달 칼럼에서는 간단한 테스트 자동화 프로그램을 작성하여 AJAX 웹 응용 프로그램 기능을 확인하는 기법에 대해 설명하겠습니다.
몇 장의 스크린샷을 통해 이번 달 칼럼의 방향을 살펴보겠습니다. 그림 1은 AJAX 기술을 사용하는, 간단하지만 전형적인 ASP.NET 웹 응용 프로그램을 보여 줍니다. 이 응용 프로그램은 Microsoft® Windows Live™ Local과 같은 지도 검색 도구를 시뮬레이션합니다. 사용자가 방향 단추 컨트롤 중 하나를 클릭하면 웹 서버에서 해당 지도 이미지를 가져와 중앙에 새 지도를 표시합니다. 스크린샷에 명확하게 나타나지 않는 부분은 웹 응용 프로그램이 Microsoft ASP.NET AJAX를 사용하여 비동기적으로 지도 이미지를 보내고 검색한다는 점입니다. 물론 실제 웹 응용 프로그램이 훨씬 더 복잡하겠지만 여기서 설명하는 기법은 복잡한 응용 프로그램에도 손쉽게 적용할 수 있으며 구현 기술에 관계없이 모든 AJAX 지원 응용 프로그램에서 작동합니다.
그림 1의 응용 프로그램을 수동으로 테스트하는 것은 비효율적이고 지루한 작업이며 시간도 많이 소모됩니다. 보다 효율적인 테스트 방식은 그림 2와 같이 간단한 테스트 자동화 프로그램을 작성하는 것입니다. 테스트 프로그램은 두 개의 프레임으로 구성되는 간단한 HTML 페이지입니다. 오른쪽 프레임은 웹 응용 프로그램을 호스팅하며 수정이나 조작이 이루어지지 않습니다. 왼쪽 프레임은 Internet ExplorerDOM(문서 개체 모델)을 사용하여 오른쪽 프레임의 웹 응용 프로그램을 조작하는 JavaScript 코드가 포함된 단일 HTML 페이지를 호스팅합니다.
이 테스트 기법은 비동기 작업을 사용하는 응용 프로그램을 처리하도록 설계되었지만 기존의 동기 HTTP 요청/응답 메커니즘을 사용하는 웹 응용 프로그램에도 적용됩니다. 이번 칼럼에서는 테스트 대상 웹 응용 프로그램에 대해 간략하게 설명하여 기존 테스트 자동화 기법이 AJAX 응용 프로그램에서 비효율적인 이유를 알아보겠습니다. 그 다음 그림 2의 이미지를 생성한 테스트 프로그램을 세부적으로 설명하고 여기서 제시되는 기법을 각자의 요구에 맞춰 수정 및 확장하는 방법을 살펴보겠습니다. 결과적으로 이 기법은 유용한 도구로 개발자와 테스터의 기술 지식을 보완해줄 것입니다.
그림 1 테스트 대상 AJAX 웹 응용 프로그램
테스트 대상 웹 응용 프로그램
필자는 AJAX 응용 프로그램 개발 작업을 훨씬 간편하게 해주는 ASP.NET AJAX 코드 라이브러리를 사용하여 그림 1과 같은 AJAX 지도 응용 프로그램을 만들었습니다. 실제 지도 이미지를 사용하는 대신 1-9 숫자를 포함하는 9개의 더미 이미지(1.jpg에서 9.jpg까지)를 사용했습니다. Microsoft TerraServer를 사용하여 실제 지도 이미지를 얻는 예제는 이번 호에 있는 Jeff Prosise의 Wicked Code 칼럼을 참조하십시오. 필자의 축소판 세계 지도는 3x3 격자로 구성되어 있습니다. 첫째 행이 1, 2, 3, 둘째 행이 4, 5, 6, 셋째 행이 7, 8, 9입니다. 5가 전체 지도의 중심이며 사용자가 5에서 North 컨트롤 단추를 클릭하면 2가 표시됩니다. North 단추의 논리는 그림 3에서 볼 수 있습니다.
현재 지도가 전체 지도의 첫째 행(1, 2, 3)에 있을 때 North 단추를 클릭하면 아무런 효과가 없습니다. 그 외의 행에 있는 경우에는 현재 지도 위치의 숫자를 가져와 3을 빼 새로운 JPEG 이미지를 결정한 다음 Image 컨트롤의 ImageUrl 속성을 업데이트합니다. South, East 및 West 단추 컨트롤의 처리기도 이와 비슷합니다.
여기서는 응용 프로그램 코드를 간단하고 읽기 쉽게 만들기 위해 의도적으로 모범적인 코딩 방법을 사용하지 않았다는 점을 유의하십시오. 실제 웹 응용 프로그램에서 코드 논리는 백엔드 SQL 또는 XML 저장소에 있는 데이터를 액세스하여 가져오고 이 데이터를 사용하여 응용 프로그램 상태를 업데이트할 것입니다. 이 칼럼의 기법에서는 Internet Explorer DOM을 통해 웹 응용 프로그램을 테스트하므로 응용 프로그램의 상태를 확인하는 방식이 중요하지 않습니다. 사용자 입력이 발생하면 응용 프로그램 상태가 변경되어 응용 프로그램의 UI에 반영되는데, 이는 사용자가 Internet Explorer DOM을 통해 액세스할 수 있습니다.
일반적인 비 AJAX 방식은 웹 서버로 HTTP 요청을 게시하고 Form 개체나 쿼리 문자열로 요청 정보를 전달합니다. AJAX 기술을 사용하지 않아도 지도 응용 프로그램이 작동하지만 이 경우 두 가지 단점이 있습니다. 첫째, HTTP 요청/응답 메커니즘은 동기식이므로 웹 서버에서 요청을 처리하여 클라이언트로 응답을 반환하는 동안 사용자가 웹 응용 프로그램과의 상호 작용 기능 중 대부분을 사용할 수 없습니다. 둘째, 대부분의 경우 HTTP 요청은 전체 응답 페이지 생성으로 이어지기 때문에 Internet Explorer에서 응답을 받으면 전체 페이지가 다시 그려집니다. 요청/응답 속도가 비교적 빠른 경우에는 짜증나는 페이지 깜박임 현상이, 속도가 느린 경우에는 이보다 더 심한 빈 페이지 현상이 발생합니다.
AJAX는 이러한 문제를 모두 해결합니다. AJAX는 HTTP 요청 대신 XMLHTTP 요청을 보냅니다. XMLHTTP 요청은 비동기식이므로 XMLHTTP 요청이 처리되는 동안 사용자는 웹 응용 프로그램과 계속 상호 작용할 수 있습니다. 또한 XMLHTTP 응답을 받으면 전체 페이지를 다시 그리는 것이 아니라 Internet Explorer DOM을 통해 웹 페이지에서 새 데이터를 포함하는 부분만 다시 그리게 됩니다.
필자는 ASP.NET 지도 응용 프로그램에서 AJAX를 사용하기 위해 "원시" JavaScript를 작성하는 대신 Microsoft ASP.NET AJAX 프레임워크를 사용했습니다. 이 프레임워크는 Visual Studio® AJAX 웹 사이트 템플릿을 제공하기 때문에 매우 쉽게 사용할 수 있습니다. 이 템플릿을 선택하면 필요한 어셈블리 참조가 웹 응용 프로그램 프로젝트에 추가됩니다. 지도 응용 프로그램에서 AJAX 기능을 사용하기 위해 소스 파일에 다음 태그를 추가했습니다.
<asp:ScriptManager ID="sm" runat="server" />
이후 다음과 같이 ASP.NET AJAX UpdatePanel 컨트롤에 Image 컨트롤(비동기 요청-응답 후에 업데이트할 컨트롤)을 래핑했습니다.
<asp:UpdatePanel ID="up1" runat="server">
<ContentTemplate>
<asp:Image ID="Image1" runat="server" ImageUrl="~/5.JPG"
(other attributes omitted) />
</ContentTemplate>
<Triggers>
<asp:AsyncPostbackTrigger ControlID="Button1" EventName="Click" />
<asp:AsyncPostbackTrigger ControlID="Button2" EventName="Click" />
</Triggers>
</asp:UpdatePanel>
여기까지가 전부입니다! XMLHTTP 개체 만들기, 비동기 응답 수신, 오류 처리, 브라우저 간 차별화와 같은 복잡한 세부 사항은 모두 프레임워크에서 자동으로 처리합니다.
4개 방향 컨트롤 모두가 아니라 Button1(North) 및 Button2(South) 컨트롤에 대한 클릭 이벤트에만 AJAX 비동기 요청/응답을 설정했다는 점을 유의하십시오. 이번에 소개한 테스트 기법이 비동기 요청과 동기 요청에 모두 적용된다는 점을 보여 주기 위해 이렇게 처리했습니다. 이 기사에 포함된 코드 다운로드를 사용하여 해당 응용 프로그램을 직접 시험해 보면 North/South 요청(AJAX)과 East/West 요청(비 AJAX) 간에 상당한 성능 차이가 있음을 알 수 있습니다.
대부분의 기존 테스트 자동화 기법은 AJAX 웹 응용 프로그램에서 작동하지 않습니다. 웹 응용 프로그램 기능을 테스트하는 가장 기본적인 방법은 사용자 입력으로 생성된 요청에 해당하는 HTTP 요청을 프로그래밍 방식으로 웹 서버에 보내고 HTTP 응답을 가져온 다음 해당 응답을 검사하여 성공/실패 결과를 확인하는 것입니다. AJAX 응용 프로그램에서는 특수한 XMLHTTP 요청을 사용하기 때문에 이 방식이 통하지 않습니다.
또 다른 일반적인 방식은 JavaScript로 Internet Explorer DOM을 조작하여 웹 서버로 요청을 보내고 onload 이벤트(클라이언트에서 해당 응답이 수신 및 로드되었음을 알림)가 발생할 때까지 대기한 다음 JavaScript와 Internet Explorer DOM을 사용하여 웹 페이지의 새 상태를 검사하여 성공/실패 결과를 확인하는 것입니다. 이 방식의 문제는 AJAX가 비동기로 작동하므로 클라이언트에서 응답을 수신했음을 알리는 onload 이벤트를 사용할 수 없다는 점입니다. 그러나 이 방식을 수정하여 AJAX 응용 프로그램의 간단한 테스트 자동화 프로그램을 작성할 수 있습니다. 응답 수신을 알리는 onload 이벤트를 사용하는 대신에 응용 프로그램에서 예상되는 상태 변화를 감시한 다음 callback 함수로 컨트롤을 이전하는 코드를 작성하면 됩니다.
테스트 자동화
그림 2의 스크린샷에서 볼 수 있는 AJAX 응용 프로그램 테스트 프로그램 시스템은 3개 파일로 구성됩니다. 그림 4는 이 프로그램의 전체 구조에 대한 블록 다이어그램을 보여 줍니다. TestHarness.aspx 프로그램 자체는 2개의 프레임으로 구성되는 간단한 웹 페이지입니다. 오른쪽 프레임에는 테스트 대상 AJAX 웹 응용 프로그램이, 왼쪽 프레임에는 테스트 대상 웹 응용 프로그램을 조작하고 검사하기 위해 최소한의 UI와 모든 JavaScript 코드가 포함된 테스트 시나리오 페이지가 있습니다. 테스트 프로그램과 테스트 시나리오 페이지에 기본 HTML을 사용할 수도 있지만 필자는 일관성을 위해 3개 페이지를 모두 .aspx 파일로 만들기로 했습니다. TestHarness.aspx 페이지의 전체 소스는 다음과 같습니다.
<html>
<head>
<title>Test Harness for AJAX Web Apps</title>
</head>
<frameset cols="45%,*">
<frame src="http://localhost/AjaxTest/TestScenario001.aspx"
name="leftFrame">
<frame src="http://localhost/AjaxApplication/Default.aspx"
name="rightFrame">
</frameset>
</html>
여기서 볼 수 있듯이 기본 테스트 프로그램 페이지는 하나의 컨테이너 파일일 뿐입니다. 테스트 시나리오는 asyncCall 프로그램 정의 함수를 호출하여 시작됩니다. 그러면 테스트 대상 응용 프로그램은 웹 서버로 비동기 XMLHTTP 요청을 보내고 delay 프로그램 정의 함수도 호출합니다. delay 함수는 웹 서버에서 해당 요청을 처리하고 비동기 응답을 반환하는 동안 순환하면서 웹 응용 프로그램 상태를 빈번히 조사합니다. 클라이언트에서 응답이 수신되고 응용 프로그램 상태가 업데이트된 후에는 delay 함수가 웹 페이지 상태 변화를 찾으며 새 비동기 호출이 수행될 수 있습니다.
asyncCall 내부
AJAX 테스트 자동화 기법의 핵심은 함께 작동하는 한 쌍의 프로그램 정의 함수, 즉 asyncCall과 delay입니다. asyncCall 메서드는 다음과 같습니다.
function asyncCall(action, checkFunc, arg, callback, pollTime)
{
numTries = 0;
action();
window.setTimeout("delay(" + checkFunc + ", " + "'" + arg +
"'" + ", " + callback + ", " + pollTime + ")", pollTime);
}
여기서는 5개의 인수를 전달합니다. 첫 번째 action 매개 변수는 Internet Explorer DOM을 사용하여 비동기 XMLHTTP 요청을 시작하는 동작을 실행하는 특정 루틴에 대한 함수 포인터입니다. 두 번째 checkFunc 매개 변수는 웹 응용 프로그램 상태가 비동기 응답이 완료되었음을 나타내는 경우 true를 반환하는 루틴에 대한 함수 포인터입니다. 세 번째 arg 매개 변수는 checkFunc 함수로 전달되는 인수입니다. 네 번째 callback 매개 변수는 비동기 응답이 완료되었을 때 호출할 루틴에 대한 함수 포인터입니다. 마지막 pollTime 매개 변수는 delay 파트너 함수의 호출 간에 대기할 시간(밀리초)을 지정합니다.
asyncCall 함수 내에서 먼저 numTries 전역 카운터를 0으로 설정합니다. 비동기 응답이 발생했음을 나타내는 상태를 찾기 위한 시도를 지정한 횟수만큼 수행한 후 종료할 수 있도록 이 변수를 사용하여 delay 함수를 시작한 횟수를 추적합니다. 그 다음 action 인수를 호출하여 비동기 요청이 발생되도록 합니다. 함수 이름에 붙는 괄호는 함수를 호출하는 구문 체계입니다.
진짜 트릭은 지금부터입니다. 내부 window.setTimeout 함수를 호출합니다. setTimeout 함수는 두 가지 인수를 받습니다. 첫 번째 인수는 한 번만 실행하기 위한 JavaScript 문이고, 두 번째 인수는 첫 번째 인수를 실행하기 전에 대기하는 시간(밀리초)입니다. asyncCall 함수의 코드를 보면 setTimeout에 대한 첫 번째 인수가 다음과 같이 확인됨을 알 수 있습니다.
delay(checkFunc, 'arg', callback, pollTime)
두 번째 인수는 pollTime입니다. 간단히 말해서 pollTime(밀리초)만큼 대기한 후에 delay 프로그램 정의 함수를 호출하는 것입니다.
asyncCall 함수 호출은 다음과 같습니다.
asyncCall(clickNorth, imgIsTwo, "2", clickSouth, 200);
이 호출은 "clickNorth라는 함수를 호출한 다음 시간 지연 루프로 이동하여 200밀리초마다 '2' 인수로 imgIsTwo 함수를 호출하고, 최종적으로 이 함수에서 true를 반환하면 clickSouth라는 함수로 컨트롤을 이전한다"로 해석될 수 있습니다.
asyncCall 메서드의 정의에서 arg를 둘러싼 작은따옴표 문자가 중요합니다. asyncCall 정의에 작은따옴표가 없으면 해당 매개 변수는 다음과 같이 참조로 전달됩니다.
asyncCall(doThis, findX, "X", doThat, 200);
이러한 호출은 X 변수가 정의되지 않았다는 오류를 일으킵니다. 작은따옴표가 있으면 이 시나리오에 맞게 값에 따라 해당 매개 변수가 전달됩니다.
asyncCall 내부에서 window.setTimeout 함수를 호출하는 흥미로운 다른 방법도 있습니다. 일반적으로는 다음과 같이 작성합니다.
window.setTimeout("delay(" + checkFunc + ", " + "'" + arg +
"'" + ", " + callback + ", " + pollTime + ")", pollTime);
이 대신 JavaScript의 익명 함수 기능을 사용하여 다음과 같이 작성할 수 있습니다.
window.setTimeout(
function(){delay(checkFunc, arg, callback, pollTime);},
pollTime);
따옴표 문자와 문자열 연결이 제거되었으므로 이 코드는 비익명 방식보다 조금 더 깔끔합니다. 또한 비익명 방식의 경우 브라우저가 새 스크립트 환경을 만들어 해당 스크립트를 처리해야 하므로 이 코드가 조금 더 효율적입니다. 그러나 arg 인수와 checkFunc 인수를 구분하는 작은따옴표 문자가 없으면 arg 인수가 값에 따라 전달되는지가 명확하게 드러나지 않습니다. 간단한 주석을 첨부하면 이로 인한 혼란을 방지할 수 있을 것입니다.
delay 함수의 내부
asyncCall 함수의 파트너인 delay 함수는 다음과 같습니다.
function delay(checkFunc, arg, callback, pollTime)
{
++numTries;
if (numTries > maxTries) finish();
else if (checkFunc(arg)) callback();
else window.setTimeout("delay(" + checkFunc + ", " + "'" + arg +
"'" + ", " + callback + ")", pollTime);
}
delay 함수는 호출하는 asyncCall 함수로 전달된 마지막 4개 인수와 정확히 일치하는 4개 인수를 받아들입니다.
delay 함수 내에서 먼저 numTries 전역 카운터를 증분합니다. 이 카운터는 delay 함수를 시작한 횟수를 추적하거나 비동기 응답의 완료 여부를 알기 위해 웹 응용 프로그램 상태를 확인한 횟수를 추적하는 데 사용됩니다. 이 전역 카운터가 전역 maxTries 상수보다 크면 웹 응용 프로그램이 서버측에서 제한 시간을 초과했거나 XMLHTTP 응답이 잘못된 것입니다. 따라서 상황을 파악하기 위해 finish 함수로 컨트롤을 이전합니다.
지연이 제한 시간을 초과하지 않은 경우 checkFunc 함수를 호출하여 올바른 응답을 나타내는 조건이 true인지 테스트합니다. checkFunc 함수에서 true를 반환하면 callback 함수를 호출하여 테스트 시나리오를 계속 실행할 수 있습니다. 그러나 checkFunc 함수에서 false를 반환하면 계속 대기하다가 내부 setTimeout 함수를 다시 호출하여 pollTime(밀리초) 동안 대기한 후 delay 함수를 다시 호출합니다.
delay 함수는 자체를 직접 호출하지 않으므로 재귀적이라고 하긴 어렵지만 setTimeout 함수를 통해 간접적으로 자체를 호출하므로 자체 참조적입니다. 이 함수의 실제적 효과는 컨트롤을 finish 함수(지연 루프에서 최대 횟수를 초과한 경우)나 callback 함수(웹 응용 프로그램 상태가 true가 된 경우)로 이전하는 대기 루프입니다. 이 방식은 결과만 놓고 보면 명확하지만 해결 방법을 직접 확인하기 전에는 명확하게 보이지 않습니다. 물론 다른 방식들도 있지만 이 방법이 실제로 간단하고 효과적이라는 점이 입증되었습니다.
asyncCall 함수에서와 마찬가지로 명명된 함수 인수를 사용하여 window.setTimeout 함수를 호출하는 대신 다음과 같이 익명 함수 기능을 사용할 수 있습니다.
window.setTimeout(
function(){delay(checkFunc, arg, callback);},
pollTime);
테스트 페이지 작성
asyncCall 및 delay 함수가 처리되므로 이제 테스트 시나리오 페이지를 작성할 수 있습니다. 테스트 시나리오의 전체 구조는 그림 5에 나와 있습니다.
테스트 시나리오 페이지의 <body> 섹션은 제목, 주석을 표시하기 위한 ID "comments"가 있는 textarea, 자동화를 시작하기 위한 단추 컨트롤 등으로 구성됩니다. <head> 섹션에는 모든 JavaScript 코드가 포함됩니다. delay 함수를 시작하는 최대 횟수를 지정하기 위해 전역 maxTries 상수를 선언하여 초기화하고 비동기 응답이 완료되었음을 알리는 조건을 확인합니다. numTries 전역 변수는 delay 함수를 시작한 횟수를 추적합니다. polling 전역 상수는 delay 함수에 대한 연속 호출 간 지연 시간을 설정합니다.
테스트 대상 AJAX 웹 응용 프로그램의 복잡성에 따라 maxTries 및 polling 값을 수정해야 할 수도 있습니다. 여기서는 호출 간 200밀리초 지연 시간으로 10회이므로 총 2초인데, 이는 웹 서버에서 XMLHTTP 요청을 처리하고 클라이언트에 응답을 반환하기에 충분하지 않을 수 있습니다.
테스트 시나리오는 runTest 함수를 호출하는 것으로 시작됩니다.
function runTest()
{
try
{
logRemark("Test Scenario #001");
logRemark("Starting test run\n");
step1(); // starting at 5, go N to 2
}
catch(ex) { logRemark("Fatal error: " + ex); }
}
}
여기서는 logRemark 프로그램 정의 함수로 프로그램 본문의 <textarea>에 몇 가지 메시지를 표시하고 step1 함수로 컨트롤을 이전하기만 하면 됩니다. 테스트 실행 중에 발생되는 예외가 포착되도록 간단한 try/catch 블록에 코드를 래핑했습니다. 필자가 사용하는 logRemark 함수는 다음과 같이 매우 간단합니다.
function logRemark(comment)
{
var currComment = document.all["comments"].value;
var newComment = currComment + "\n" + comment;
document.all["comments"].value = newComment;
}
여기서는 ID가 "comments"인 <textarea> 요소의 현재 내용을 검색하고 현재 내용에 줄바꿈 문자와 새로운 주석 텍스트를 추가하며 <textarea> 내용을 업데이트된 주석 집합으로 대체합니다. 효율적이지는 않지만 간단한 테스트 자동화에서는 단순성이 효율성보다 더 중요한 고려 사항인 경우가 많습니다.
step1 함수는 다음과 같이 테스트 대상 AJAX 웹 응용 프로그램의 조작을 시작합니다.
function step1()
{
logRemark("Clicking North, waiting for '2'");
asyncCall(clickNorth, checkImageSrc, "2", step2, polling);
}
주석을 기록한 다음, 이번 테스트 자동화의 핵심인 asyncCall 함수를 호출합니다. 이 호출은 clickNorth 함수를 호출한 후 200밀리초 간격으로 확인하면서 checkImgSrc("2")에서 true를 반환할 때까지 시간 지연 루프를 호출한 다음 step2 함수로 컨트롤을 이전하는 역할을 합니다. 시간 지연 루프 호출이 10회(maxTries)를 초과하면 컨트롤이 finish 함수로 이전됩니다.
clickNorth 함수는 다음과 같이 매우 간단합니다.
function clickNorth()
{
var btnNorth = parent.rightFrame.document.all["Button1"];
if (!btnNorth) throw "Did not find btnNorth";
btnNorth.click();
}
여기서는 Internet Explorer DOM을 사용하여 Button1 컨트롤에 대한 참조를 가져온 다음 click 메서드를 호출합니다. 자동화 프로그램과 AJAX 응용 프로그램이 각각 다른 프레임에 있기 때문에 테스트 스크립트에서 응용 프로그램의 컨트롤에 액세스하려면 상위 키워드를 사용하여 한 수준 "위로" 이동한 다음 응용 프로그램 컨테이너의 프레임 ID를 사용해야 합니다. 필자는 document.all 컬렉션을 사용하여 웹 페이지 컨트롤에 대한 참조를 가져오는 것을 선호하지만 다음과 같이 getElementById 메서드를 사용하는 것을 선호하는 사람들도 있습니다.
var btnNorth = parent.rightFrame.document.getElementById("Button1");
checkImageSrc 함수는 테스트 대상 AJAX 응용 프로그램의 Image1 컨트롤이 비동기 응답으로 업데이트되면 이를 자동화 프로그램에 알립니다.
function checkImageSrc(target)
{
try
{
var s = parent.rightFrame.document.all["Image1"].src;
return s.indexOf(target) >= 0;
}
// traps case where Image1 is not yet loaded
// logRemark("Error in checkImageSrc(): " + ex);
catch(ex) { return false; }
}
여기서는 속성에 대상 문자열이 있는지 확인하기 위해 기본적으로 Image1 컨트롤의 src 속성만 검사합니다. 예를 들어 지도 2에 사용한 파일 이름인 "~/2.jpg"와 같이 "2"가 이미지 src 속성에 있으면 checkImageSrc("2") 호출은 true를 반환합니다. 자신만의 AJAX 자동화 프로그램을 작성할 때는 적절한 확인 함수를 만들어야 합니다. 예를 들어 웹 응용 프로그램에서 ID가 "TextBox1"인 TextBox 컨트롤을 업데이트한다고 가정해 봅시다. 이때 사용할 수 있는 확인 함수는 다음과 같습니다.
function checkTextBoxValue(target)
{
try
{
var s = parent.rightFrame.document.all["TextBox1"].value;
return s.indexOf(target) >= 0;
}
// traps case where TextBox1 is not yet loaded
// logRemark("Error in checkTextBoxValue(): " + ex);
catch(ex) { return false; }
}
checkImageSrc 함수에는 try/catch 블록이 있습니다. try/catch는 대부분 예기치 않은 오류 조건을 포착하는 데 사용되지만 여기서는 일반 함수 논리의 일부로 이 블록을 사용하고 있습니다. 이는 웹 페이지를 다시 그리는 동안에는 Image1 컨트롤에 대한 참조를 얻을 수 없으며 이를 시도할 경우 예외가 발생한다는 개념입니다. 이는 반드시 웹 응용 프로그램의 상태가 잘못되었음을 의미하지는 않으며 단지 불완전한 상태임을 의미할 수도 있으므로 예외를 포착하여 false를 반환하려는 것입니다. logRemark 호출에 대한 주석을 풀고 폴링 시간을 매우 짧게 하면(예: 10밀리초) 이 동작을 관찰할 수 있습니다.
try/catch를 일반 함수 논리의 일부로 사용하는 것은 대개 좋지 않은 코딩 스타일로 간주되지만, 여기에서는 단순함으로 얻는 이득이 try/catch를 일반 논리 흐름에 사용할 이유가 되기에 충분합니다.
이 테스트 시나리오 스크립트에서는 step2, step3, step4 및 step5 함수를 step1 함수와 거의 동일하게 정의합니다. step2 함수는 clickEast 함수를 호출하고 "3"이 지도 표시 영역에 나타날 때까지 대기한 다음 step3 함수를 호출합니다. step3 함수는 clickSouth 함수를 호출하고 "6"이 나타날 때까지 대기하며, 나머지 함수들도 같은 방식입니다. step5 함수는 다음과 같이 간략한 중간 단계의 finish 함수인 step6을 호출합니다.
function step6()
{
finish();
}
여기서는 컨트롤을 실제 finish 함수로 이전합니다(그림 6 참조). finish 함수는 delay 함수 호출에 허용된 최대 횟수를 초과한 상황도 처리한다는 점을 상기하십시오.
먼저 numTries 전역 카운터가 maxTries 전역 상수를 초과한 상황을 처리합니다. 이러한 상황이 발생하는 경로는 두 가지입니다. AJAX 응용 프로그램의 논리는 올바르지만 응용 프로그램에서 페이지 상태를 업데이트하는 데 너무 많은 시간을 소모하는 경우나 응용 프로그램의 논리가 잘못되어 확인 함수에서 정확한 상태를 확인하지 못하는 경우입니다. 여기서는 테스트 시나리오 실패에 대한 간단한 해결 방안을 임의로 선택했습니다.
finish 함수의 두 번째 논리 분기는 응용 프로그램의 최종 상태를 확인하여 성공/실패 결과를 판단합니다. 여기서는 "5"에서 시작한 다음 North, East, South, South 및 West 컨트롤을 연속하여 클릭하는 경우 최종 지도 이미지가 "8"이 맞는지 확인합니다.
테스트 프로그램 확장
여기서 제시하는 테스트 자동화 시스템은 매우 간단하며 사용자가 수정할 수 있도록 설계되었습니다. 보통 Magazine 기사와 마찬가지로 이 기사에서도 기본 개념에 집중하기 위해 대부분의 오류 확인 과정을 생략했습니다. 그러나 사용자는 오류 트랩을 자유롭게 추가하고 싶을 것입니다. 테스트 자동화는 본질적으로 다루기 까다로우며 오류는 예외보다는 규칙에 가깝습니다.
테스트 프로그램은 완전하게 자동화되지는 않은 상태입니다. 자동화 프로그램을 시작하려면 Run Test 단추를 직접 클릭해야 합니다. 프로그램을 완전하게 자동화할 수 있는 방법에는 여러 가지가 있습니다. 한 가지 간단한 방법은 다음과 같이 onload 처리기를 테스트 프로그램 프레임에 추가하는 것입니다.
<frame src="http://localhost/AjaxApplication/Default.aspx"
name="rightFrame"
onload="leftFrame.launch();" >
이 경우 이 테스트 시나리오 코드의 launch 함수는 다음과 같이 정의됩니다.
var started = false;
function launch()
{
if (!started) runTest();
}
또한 이 칼럼에서 설명한 runTest 함수를 다음 문으로 시작하도록 편집합니다.
started = true;
테스트 대상 AJAX 웹 응용 프로그램이 포함된 오른쪽 프레임이 로드되면 launch 함수가 호출됩니다. launch가 처음 실행되면 started 전역 변수가 false가 되며 컨트롤이 runTest 함수로 이전됩니다 그러면 runTest 함수에서 started 변수를 true로 설정한 다음 자동화 프로그램을 실행합니다. 이후 정기적인 HTTP 요청/응답 동작에 대해 웹 응용 프로그램의 후속 페이지가 로드되며 launch 함수가 호출되지만 started 전역 변수가 true이므로 컨트롤은 runTest 함수로 다시 이전되지 않습니다. 완전히 자동화된 테스트 프로그램을 사용하면 여러 개의 프로그램 페이지와 시나리오 페이지를 만든 후, 다음과 같은 문을 BAT 파일에 배치하고 Windows 작업 스케줄러를 통해 자동화 프로그램을 시작하여 여러 시나리오를 실행할 수 있습니다.
iexplore http://localhost/AjaxTest/TestHarness01.aspx
iexplore http://localhost/AjaxTest/TestHarness02.aspx
필자의 간단한 시스템에서는 시스템을 편리하게 매개 변수화할 수 없다는 것이 단점입니다. 테스트 시나리오 페이지의 일반 JavaScript 코드를 별도의 단일 .js 파일 안에 쉽게 넣을 수 있지만 단일 테스트 프로그램 페이지에서 여러 테스트 시나리오를 실행하는 작업을 편리하게 조율할 방법이 없습니다. 즉, 불가능한 것은 아니지만 쉬운 작업이 아닙니다.
AJAX 테스트 자동화 프로그램의 또 다른 확장은 테스트 시나리오 결과 저장 프로세스를 자동화하는 것입니다. 여기에 제시된 시스템에서는 결과를 기록하지 않지만 여러 가지 방법으로 기록할 수 있습니다. 한 가지 방식은 다음과 같이 Form 요소를 테스트 시나리오 페이지에 배치하고 테스트 시나리오 결과를 보관하는 텍스트 필드를 폼 안에 넣은 후 다음과 같이 서버로 결과를 게시하는 것입니다.
<form name="resultForm" method="Post" action="saveResults.aspx">
<p>Result: <input type="text" name="result"></p>
<p><input type="submit" name="saver" value="Save Results"></p>
</form>
finish 함수 내에서 이 코드 행을 따라 result 필드에 pass/fail 값을 제공할 수 있습니다. if (is.indexOf("8") >= 0)
{
logRemark("\n*Pass*");
theForm.result.value = "Pass";
}
else
{
logRemark("\n*FAIL*");
theForm.result.value = "Fail";
}
이제 Save Results 단추를 클릭하여 시나리오 결과를 수동으로 저장하거나 시나리오 코드에 다음 코드를 넣어 저장 프로세스를 자동화할 수 있습니다.
document.all["theForm"].submit();
요약
필자가 제시한 테스트 프로그램은 AJAX 웹 응용 프로그램을 테스트하는 여러 가지 방법 중 하나일 뿐입니다. 이 칼럼을 읽고 나면 필자의 간단한 방식은 물론 이보다 정교한 프로그램을 사용한 AJAX 웹 응용 프로그램 테스트도 별 어려움 없이 수행할 수 있을 것입니다. ASP.NET AJAX 프레임워크의 릴리스에 힘입어 AJAX 웹 응용 프로그램의 확산 속도도 더 빨라질 것으로 예상됩니다. 간단한 테스트 자동화 프로그램을 작성할 수 있는 능력의 중요성도 점차 높아지고 있는 만큼 여러분의 기술 지식에 유용한 도구로 추가될 것입니다.
Tack 2007/02/22 14:47
^^ 좋은 글입니다.
날개달기 2007/03/10 01:51
답변이 늦었네요. 감사합니다. (^ㅡ^)