밥하는 거 하고 설거지 하는 거 하고 어느 것이 더 싫은 가 하면 설거지죠.

마찬가지로 프로그램 작성하는 거 하고 프로그램 버그 테스팅하는 거 하고

어느 것이 더 싫은 가 하면 절대적으로 버그 테스팅이죠..

하지만 프로그램을 작성할 때 여기서 벗어날 방법이 없죠.

특히 사용자 인터페이스가 있는 프로그램을 테스트 할 때 그 귀찮음이란...

웹 인터페이스는 더 귀찮습니다. 

브라우저에 주소 치고 , 클릭하고 , 로긴 하고, 해당 웹페이지 테스트 까지..

그걸 한 줄 고치고 시험하고 ... 

지긋 지긋 합니다. 

자동으로 내가 원하는 거 하는 거 없나?

이런 생각 드시죠?

이때 강추 하는 것이 CasperJS  입니다.

그럼 이 CasperJS 가 뭐가 하면요...

웹 브라우져 입니다. 

엥?

웹 브라우져?

예!

화면없이 브라우저 처럼 웹 페이지를 접근해서 

데이터 형태로 읽을 수 있고 마우스 와 키보드의 입력을 줄 수 있는 것..

이게  CasperJS 가 아니고...PhantomJS 인데..

이 PhantomJS 를 좀 더 쓰기 좋게 만든 것이 CasperJS 입니다. 

PhantomJS 가 진짜이긴 하지만 좀 사용하기 힘드는데 반해 

CasperJS 는 쉽습니다. 

더구나 익숙한 자바스크립트로 접근합니다. 

어찌되었든!

백문이 불여 일견이라고 ...

직접 따라해 봅시다. 

우선 설치해야 겠죠?

아.. 이 후 진행은 우분투 환경에서 진행한다는 가정하에 설명합니다. 

다른 환경까지 다루는 것은 흠.. 제가 귀찮잖아요.. 해 봐야 하는데 ^^;

우선 CasperJS 의 홈페이지는 다음과 같습니다. 

http://casperjs.org/

여기에 가시면 사용에 관련된 문서가 있으니 영어에 자신 있는 분들은

이 글을 굳이 보지 않고도 다음을 가시면 자세한 설명이 쭈욱~~~ 있습니다. 

http://docs.casperjs.org/en/latest/

그럼 먼저 설치 부터 해 봅시다. 

그전에 node.js 가 설치되어 있어야 한다는 것을 먼저 말씀 드립니다. 

명령행에서 다음과 같이 입력합니다. 

  $ npm install -g phantomjs
$ npm install -g casperjs

진짜 심플하게 설치된답니다. 

버전을 확인해 봅시다. 

$ casperjs --version
1.1.0-beta3

도움말은 이렇게 치면 됩니다. 

$ casperjs --help
그럼 제대로 설치 되었는지 시험을 한번 할까요?

보통 가장 흔하게 하는 것이 특정 페이지를 스크린 샷을 찍는 겁니다. 

다음과 같이 자바스크립트 코드를 만들어 봅시다. 

---[A001_falinux_screen_capture.js]----------------------------------------
var 캐스퍼_모듈   = require('casper');

var 캐스퍼 = 캐스퍼_모듈.create();

캐스퍼.options.verbose = false;
캐스퍼.options.logLevel     = "debug";
캐스퍼.options.viewportSize = { width: 1024, height: 768 } ;
캐스퍼.start( 'http://www.falinux.com/kr/' , function() {
캐스퍼.echo( '접근 페이지 = ' + 캐스퍼.getCurrentUrl() );
});

캐스퍼.then(function() {
캐스퍼.echo( '화면 캡쳐' );
캐스퍼.capture( 'falinux.png' );
});

캐스퍼.run(function() {
캐스퍼.echo( '모든 작업 종료' );
캐스퍼.exit();
});
---------------------------------------------------------------------------
프로그램에서 가장 먼저 해야 할 것은 CasperJS 모듈을 선언하는 것입니다. 

var 캐스퍼_모듈   = require('casper');
그리고 CasperJS 인스턴스 생성자 함수를 선언합니다. 

var 캐스퍼 = 캐스퍼_모듈.create();

이렇게 만들어진 CasperJS 인스턴스 인 캐스퍼에 로그에 대한 옵션을 다음과 같이 설정합니다. 

캐스퍼.options.verbose = false;
캐스퍼.options.logLevel     = "debug";

이 옵션의  verbose 는 로그에 대한 표준 출력에 표출 유무를 선언합니다. 
여기서는 표출하지 않겠다는 의미 입니다. 만약 표출하더라도 logLevel 에 선언된 것에 의해서 
표출 유무가 결정됩니다. 

이 옵션에 설정할 수 있는 것은 

debug, info, warning, error
이 있습니다. 

가장 낮은 레벨은 debug 입니다. 가장 높은 것은 error 입니다. 

즉 logLevel 에 debug 를 설정하고 

log() 함수에 info 를 설정하고 수행하면 해당 메시지는 표출됩니다. 

캐스퍼.options.logLevel     = "debug";
캐스퍼.log( '보이 십니까?', "info" );

반면 logLevel 에 info 를 설정하고 
log() 함수에 debug 를 설정하고 수행하면 해당 메시지는 표출 되지 않습니다 .

캐스퍼.options.logLevel     = "info";
캐스퍼.log( '안 보일 걸요?', "debug" );

화면에 무조건 메세지를 표시하려면 다음과 같이 수행하면 됩니다. 

캐스퍼.echo( '화면 캡쳐' );

즉 edho() 함수는 화면에 표출하는 함수 입니다. 

viewportSize 옵션은 웹에 접근하고 웹 화면을 랜더링하기 위한 가상 화면 크기 입니다. 

여기서는 1024 X 768 크기의 크기를 지정하고 있습니다. 

이 크기는 브라우저의 클라이언트 영역을 지정합니다.

CasperJS 에서 가장 먼저 선언해야 하는 함수는 start() 함수 입니다. 

이 함수는 아규먼트 첫번째 인자로 접속 하려는 URL을 지정하고 접속이나 에러가 났을때 호출되는 

콜백 함수를 선언합니다. 

접속이 끝났을 때 다음 처리 함수를 지정하려면 then() 함수를 사용합니다. 

우리는 여기서 캡쳐 하기 위한 함수 인 capture() 를 호출하게 됩니다. 

capture() 는 저장하려는 파일명을 지정하고 옵션으로 특정 영역을 다음과 같이 지정 할수 있습니다. 

{ top: 100, left: 100, width: 500, height: 400 }
저장되는 포맷은  png 그래픽 포맷 입니다. 

이렇게 처리를 선언하고 후 실제 처리가 되는 시점은 run()  함수가 호출되었을 때 입니다 .

모든 처리가 끝나면 run() 함수에 지정된 콜백 함수가 호출됩니다. 

이때 exit() 함수를 호출하여 프로그램을 종료하게 됩니다. 

보통 하나의 화면을 캡쳐하기 위해서 쓰일 수 있지만 실제로 이렇게 화면을 찍는 이유는 

다양한 브라우저 디스플레이 장치의 크기에 웹 페이지들이 대응 되는 가를 확인하기 위한 

경우가 많습니다. 즉 반응형 웹 일 경우에 디자인 한 웹 페이지를 실제로 수행하여 

시험해서 문제점을 확인 하기 위한 것이죠.. 

이걸 손으로 일일히 하거나 모바일 기기마다 확인한다면 무척 어렵죠..

보통은 다음과 같은 형태로 코드를 수행하면 크기 마다 어떻게 표현되는 가를 알 수 있습니다. 

---[A002_falinux_screen_capture_sizes.js]----------------------------------------
var 캐스퍼_모듈   = require('casper');

var 모바일_화면들 = [

 {
'이름': 'samsung-galaxy_y-portrait',
'크기': {가로: 240, 세로: 320}
 },
 {
'이름': 'samsung-galaxy_y-landscape',
'크기': {가로: 320, 세로: 240}
 },
 {
'이름': 'iphone5-portrait',
'크기': {가로: 320, 세로: 568}
 },
 {
'이름': 'iphone5-landscape',
'크기': {가로: 568, 세로: 320}
 },
 {
'이름': 'htc-one-portrait',
'크기': {가로: 360, 세로: 640}
 },
 {
'이름': 'htc-one-landscape',
'크기': {가로: 640, 세로: 360}
 },
 {
'이름': 'nokia-lumia-920-portrait',
'크기': {가로: 240, 세로: 320}
 },
 {
'이름': 'nokia-lumia-920-landscape',
'크기': {가로: 320, 세로: 240}
 },
 {
'이름': 'google-nexus-7-portrait',
'크기': {가로: 603, 세로: 966}
 },
 {
'이름': 'google-nexus-7-landscape',
'크기': {가로: 966, 세로: 603}
 },
 {
'이름': 'ipad-portrait',
'크기': {가로: 768, 세로: 1024}
 },
 {
'이름': 'ipad-landscape',
'크기': {가로: 1024, 세로: 768}
 },
 {
'이름': 'desktop-standard-vga',
'크기': {가로: 640, 세로: 480}
 },
 {
'이름': 'desktop-standard-svga',
'크기': {가로: 800, 세로: 600}
 },
 {
'이름': 'desktop-standard-hd',
'크기': {가로: 1280, 세로: 720}
 },
 {
'이름': 'desktop-standard-sxga',
'크기': {가로: 1280, 세로: 1024}
 },
 {
'이름': 'desktop-standard-sxga-plus',
'크기': {가로: 1400, 세로: 1050}
 },
 {
'이름': 'desktop-standard-uxga',
'크기': {가로: 1600, 세로: 1200}
 },
 {
'이름': 'desktop-standard-wuxga',
'크기': {가로: 1920, 세로: 1200}
 },

];
var 캐스퍼 = 캐스퍼_모듈.create();

캐스퍼.options.verbose = false;
캐스퍼.options.logLevel     = "debug";
캐스퍼.start();

캐스퍼.each( 모바일_화면들, function(캐스퍼, 모바일_화면) {
캐스퍼.echo( '모바일 이름 ' +  모바일_화면.이름 );
캐스퍼.then(function() {
캐스퍼.viewport( 모바일_화면.크기.가로, 모바일_화면.크기.세로 );
});
캐스퍼.thenOpen( 'http://www.falinux.com/kr/' , function() {
캐스퍼.echo( '접근 페이지 = ' + this.getCurrentUrl() );
});

캐스퍼.then(function() {
var 이미지_파일명 = 모바일_화면.이름 + '.png';
캐스퍼.echo( '화면 캡쳐 [' + 이미지_파일명 + ']' );
캐스퍼.capture( 이미지_파일명 );
});
});
캐스퍼.run(function() {
캐스퍼.echo( '모든 작업 종료' );
캐스퍼.exit();
});
---------------------------------------------------------------------------

이 소스에서 모바일_화면들 변수에 지정된 데이터는 

각 모바일 별로 화면 크기인데 조금은 도움이 돼실 겁니다. ^^;

CasperJS 에는 여러 함수가 있는데 each() 함수는 아큐번트 첫번째 인자에 지정된

배열에서 하나씩 추출해서 콜백 함수를 호출합니다. 

캐스퍼.each( 모바일_화면들, function(캐스퍼, 모바일_화면) {
:
});
여기서는 모바일_화면들 에 의해서 지정된 배열의 각 요소를 콜백 함수로 호출할때 

CasperJS 인스턴스를 첫번째 아규먼트 인자로 , 배열 요소 한개를 두번째 아규먼트 인자로 

전달합니다. 

이 호출때마다 

첫번째 then() 함수를 호출하여 viewport() 함수를 이용하여 스크린 크기를 지정하고 
두번째 thenOpen() 웹 페이지에 접속하고 
세번째 then() 함수를 호출하여 캡쳐 합니다. 
 
쉽죠?

자 이렇게 화면을 테스트 하면 

아마도 화면을 고칠 때 마다 화면 확인 하느라 고생할 필요가 없겠죠?

자 이제 이런 거 말고 진짜 귀찮은 사용자 입력을 한번 해 봅시다. 

어떤 것을 할꺼냐 하면 

특정 웹 페이지에 가서 사용자 로그인을 하고 

최종적으로 로그인 되었을 때 화면을 찍을 겁니다. 

대상 홈페이지는  forum.falinux.com 입니다. 

여기서 여러분은 구글 브라우저에서 요소 검사에서 웹 페이지의 ID 와 같은 속성을 

찾는 법은 알고 있다고 가정하겠습니다. 

---[A003_falinux_screen_capture_login.js]----------------------------------------
var 캐스퍼_모듈   = require('casper');

var 캐스퍼 = 캐스퍼_모듈.create();

캐스퍼.options.verbose = true;
캐스퍼.options.logLevel     = "debug";
캐스퍼.options.viewportSize = { width: 1024, height: 768 } ;
캐스퍼.start( 'http://forum.falinux.com/zbxe/' , function() {
캐스퍼.echo( '접근 페이지 = ' + this.getCurrentUrl() );
});
캐스퍼.then(function() {
캐스퍼.echo( '로그인' );
캐스퍼.fill('form#fo_login_widget', { 'user_id':'아무개', 'password':'확실한암호' }, true);
});

캐스퍼.thenOpen( 'http://forum.falinux.com/zbxe/' , function() {
캐스퍼.echo( '접근 페이지 = ' + this.getCurrentUrl() );
});
캐스퍼.then(function() {
캐스퍼.echo( '화면 캡쳐' );
캐스퍼.capture( 'forum_falinux.png' );
});

캐스퍼.run(function() {
캐스퍼.echo( '모든 작업 종료' );
캐스퍼.exit();
});

---------------------------------------------------------------------------

이 소스는 forum.falinux.com  에 접속한 후 

fill() 함수를 이용하여 fo_login_widget 를 셀렉터로 지정할 수 있는 폼 에서 

user_id 로 셀렉션 할 수 있는 사용자 입력에 사용자 ID를 입력하고 

password로 셀렉션 할 수 있는 암호를 넣고 폼 커밋을 발생 시켜서 정상적으로 로그 인 한 후 

thenOpen() 함수를 이용하여 캡쳐 하고자 하는 원하는  URL 을 다시 연 후 

캡쳐하는 프로그램입니다. 

소스를 보시면 아시겠지만 무척 간단 합니다. 

화면을 캡쳐 하는 것은 아무래도 웹 테스트 자동화는 아니죠..

우린 어떤 프로그램의 수정에 따라서 

수 많은 웹 화면들이 원하는 대로 정상적으로 동작하는 가를 보고 싶은 겁니다. 

이런 테스트를 손으로 클릭질을 해 간다는 것은 무식한 짓이죠 ^^

자 이제 이런 행위를 자동으로 하기 위해서 CasperJS 를 어떻게 이용할 수 있는지 

초 간단 예를 봅시다. 

아래 예제는 에프에이리눅스 포럼에 접속 한 후 로그인 폼이 있는가를 확인하는 겁니다. 

일단 테스트 스크립트를 봅시다. 

---[A003_falinux_screen_capture_login.js]----------------------------------------
var 캐스퍼 = casper;
var 총_시험_항목_수 = 2;
캐스퍼.test.comment('캐스퍼 test 처리 시험');
캐스퍼.test.begin('테스트 시작', 총_시험_항목_수, function(시험) {
캐스퍼.start( 'http://forum.falinux.com/zbxe/' , function() {
시험.assertTitle("임베디드 리눅스 시스템 포럼 - Falinux Forum",               
"에프에이리눅스  포럼 타이틀 정상 체크");
});
캐스퍼.then(function() {
시험.assertExists('form[id="fo_login_widget"]',  "로그인 폼 체크" );
});
캐스퍼.run(function() {
캐스퍼.echo( '모든 시험 종료' );
시험.done();
});
});
---------------------------------------------------------------------------

처음 화면 캡쳐 했던 소스 A001_falinux_screen_capture.js 와 조금 유사함을 볼 수 있을 겁니다.

이 소스를 실행하려면 이전 것과 조금 다르게 실행 합니다. 

$ casperjs test A004_falinux_test.js
이런 식으로 실행하게 됩니다. 

보면 아시겠지만 test 라고 하는 명령을 실행할 때 추가해서 실행합니다. 

이런 이유로 이전 소스에서는 CasperJS 모듈을 포함하는 부분이 있는데..

test 모듈은 해당 모듈을 이미 로드 한 상태로 호출되는 것이기 때문에..

var 캐스퍼_모듈   = require('casper');
var 캐스퍼 = 캐스퍼_모듈.create();

이런 부분이 없습니다. 

대신 이미 생성해서 전달되는 casper 라는 변수를 이용합니다. 

예제에서 한글로 설명하기 위해서 이 부분을 살짝 대치한 것이 

var 캐스퍼 = casper;

입니다. 

한글로 쓰고 싶은 저의 욕구 때문에 이렇게 하지만 

여러분은 굳이 이렇게 할 필요는 없습니다. 

전달된 캐스퍼 인스턴트는 내부적으로 test 라고 하는 속성을 갖는데 

이 test 가 바로 여러분이 시험하는 다양한 API를 갖게 됩니다. 

테스트 과정에 어떤 코멘트를 화면에 표출하고 싶다면 

캐스퍼.test.comment('캐스퍼 test 처리 시험');

형식으로 출력하면 됩니다. 

시험은 캐스퍼.test.begin() 함수를 이용하여 시작합니다. 

이 begin() 함수는 다음과 같은 세가지 아규먼트 인자를 갖습니다. 

캐스퍼.test.begin( 시험_시작할때_표출될_코멘트_문자열,
  시험_총_항목_수,
  시작되었을_때_수행되는 함수 );
  
시작되었을_때_수행되는 함수() 가 실제로 시험을 진행하게 됩니다. 

이때 이 함수에 전달되는 아규먼트 인자는 다음과 같습니다. 
  
function 시작되었을_때_수행되는 함수( test ) {
:
}
제 소스에서는 test 대신에 한글로 시험이라고 썼는데 

이 아규먼트는 캐스퍼.test 와 같은 것입니다. 

이 아규먼트 변수를 이용해서 시험용 API 를 호출하면 됩니다. 

수행 동작에서 첫번째 등록해서 하는 것이 

캐스퍼.start() 함수를 이용하여 접속하고자 포럼 페이지를 지정하고 

접속 되었을때 

assertTitle() 함수를 이용해서 웹 페이지 타이틀이 정상적으로 표출되는가를 

체크합니다. 당연히 이 부분에서 에러가 나면 시험은 중지 됩니다. 

캐스퍼.then() 함수를 이용해서 다음 진행을 하면 되는데 

이때 assertExists() 함수를 이용해서 로그인 폼이 있는가를 확인 합니다. 

이런식으로 접속 및 조작 과 시험 항목을 등록 하고 

최종적으로 캐스퍼.run() 함수를 이용해서 

실질적인 진행을 시작합니다. 

모든 시험이 끝나면 

시험.done();
함수를 호출하여 전체 처리를 종료 하고 레포팅을 출력 하도록 합니다. 

아주 간단하게 소개 했는데..

CasperJS 는 무척 방대하고 강력한 테스트 플랫폼을 제공합니다.

물론 처음 시험 절차를 만들어 가는 것은 무척 어렵고 지루하지만..

이 과정을 꾸준히 습관화 하면 

최종적을 프로그램이 커질수록 정말 손으로 일일히 하던 수 많은 과정을

자동화 함으로써 프로그래머들의 수명을 연장 할 수 있을 것으로 

전 확신합니다.!..