최근에 헤드리스 브라우저를 사용할 일이 있어서 찾아봤다.

제일 처음 생각난게 가장 유명한 phantomjs[링크]. 근데 난 node.js을 사용해야하기 때문에 직접 사용하진 못하고 proxy을 사용해야했다. 그래서 일단 스킵.

다음으로 찾은건 zombiejs[링크].
node.js로 만들어졌기 때문에 바로 설치해서 실행해봤다.

이게 왠걸 유니코드를 지원하지 않는다. -_-;

다시 돌아가서 phantomjs을 사용하기로.

프록시해주는 라이브러리를 찾아보니 phantomjs-node[링크], phantom-proxy[링크] 두 개가 있었다.

둘 중에 phantomjs-node가 유명한 것 같아서 써보려고 하니 문제는 phantomjs의 경로를 설정을 해야한다. 근데 난 cafe24을 사용하기 때문에 경로를 따로 설정할 수 없어 결국에 phantom-proxy로 결정했다.


좀 더 써봐야 알겠지만, 크게 문제가 되진 않네.

Posted by 전용우
,

V8 빌드하기

프로그래밍 2013. 8. 23. 02:01

V8은 크로미엄에 비해 더 간단하지만 나중을 위해 간단히 적어본다.

V8역시 mac을 기준으로 작성한다.


1. 소스 코드 받기

앞서 크로미엄[링크]에서 삽집하게된 이유가 V8을 먼저 빌드했기 때문이다. -_-

V8은 크로미엄과 다르게 파일을 빠르게 checkout[링크]받을 수 있기 때문에 난 당연히 비슷할 줄 알고 checkout받았는데 생각해보니 너무 무식했다.

git clone git://github.com/v8/v8.git v8 && cd v8

2. 코드 동기화

V8는 git을 이용해서 쉽게 최신 파일을 받을 수 있다.

git pull --rebase origin master


3. 빌드

의존파일을 빌드하기

make dependencies

본인에 환경에 맞게 빌드 설정한다. 난 x64로 실행.

build/gyp_v8 -Dtarget_arch=x64

크로미엄과 같이 xcodebuild로 빌드한다. 그리고 난 debug모드에 공유 라이브러리로 빌드한다.

xcodebuild -project build/all.xcodeproj -configuration Debug -Dcomponent=shared_library


4. 실행

정상적으로 빌드를 하면 "v8/xcodebuild/Debug"에 파일들을 확인할 수 있다. shell을 이용하여 인터렉티브하게 실행해볼 수 있고, d8로 스크립트파일을 실행할 수 있다.


자세한 내용은 링크를 참고하면 된다.


Posted by 전용우
,

사실 알고보면 별거 아닌데 대박 삽질해서 다음 빌드때 잊지 않기 위해 정리한다. 난 mac에 설치했는데 타 OS도 큰 차이 없는것 같다.

1. 소스 코드 받기

처음엔 svn으로 받아오려고 했는데 일단 시간도 정말 오래걸리고 중간에 자꾸 끊기는 현상이 발생했다.몇일 개삽질하다가 그냥 소스코드를 내려받음.(링크) 이것도 오래 걸리긴 하는데 끊김이 없다. 나는 여기서 빌드하기 위한 시간의 99% 소비했다.;;


2. 코드 동기화

크로미엄은 정말 수시로 코드가 변하기 때문에 최신 코드를 보려면 동기화를 해야 하는데  이때 gclient을 이용한다.  아래와 같이 실행한다.

gclient sync

만약에 gclient가 없다면 [링크]을 통해 설치하면 된다. 근데, 문제는 동기화하는데 시간이 무지 걸린다. 그래서 내가 받고 싶은 폴더만 받으려면 소스 코드 루트에 .gclient에 설정하면 된다.

나도 왠만하면 하루에 한번씩 하려고 했으나 하루사이에도 엄청 변경되서 요즘엔 거의 동기화 안한다. -_-;

fetch chromium --nosvn=True

1시간 20분 정도 걸림.

3. 빌드하기

최근 맥에서 기본 빌드가 ninja로 변경됬다.[링크]

그래서 아래와 같이 ninja을 이용하여 빌드하면 된다.

 ninja -C out/Debug chrome

근데 만약에 xcode로 빌드하고 싶다면 project를 xcode로 만들면 된다.

xcode로 빌드하는 방법은 build폴더에 있는 gyp_chromium파일에서 ninja를 xcode로 변경하거나 GYP_GENERATORS 환경변수를 xcode로 변경하고 gyp_chromium.py을 실행시켜 xcode프로젝트를 만든 후 아래와 같이 빌드하면 된다

xcodebuild -project chrome.xcodeproj -configuration Debug -target chrome

target을 All 지정하면 모두 빌드하기 때문에 완전 느리다. 그래서 원하는 타켓을 지정하면 된다. 난 chrome만 쓸거니깐 chrome만 빌드.


4. 실행하기

빌드가 정상적으로 완료하면 xcode로 빌드했으면 xcodebuild/Debug, ninja로 빌드했으면 out/Debug에 있는 Chromium.app을 open하면 된다.


보다 자세한 내용은 [링크]를 참고.


ps. 다음엔 비교적 삽질을 덜한 V8빌드하기. 빌드하면 아무것도 안해도 뭔가 한 것 같은 느낌이 ㅋ

Posted by 전용우
,

크롬 카나리 버전을 보니 메모리 힙을 리코딩하는 기능이 나왔다.

기존에는 메모리 프로파일은 스냅샵을 찍는거라 찍는 순간에 메모리의 상황이나 스냅샷끼리의 변화 밖에 알 수 없었다. 하지만 크롬 카나리 버전에는 메모리 힙을 리코딩 하는 기능이 추가되어 메모리 힙의 상황을 시간에 흐름에 따라 확인 할 수 있어 프로파일링 하는데 좋아졌다.


사용방법은 간단하다.

프로파일탭(profiles)에서 힙 사용량 기록하기(Record Heap Allocations)을 아래와 같이 선택하고 누른다.




그리고 적절한 시점에 기록하기를 멈추면 아래와 같이 결과를 확인할 수 있다.




결과에서 파란바는 메모리 힙에 할당된 그래프이며 회색은 해제된 그래프이다.

파란바를 확인하여 증가된 메모리를 찾아 개선할 수 없는지 확인하면 된다.

(속성에 대한 자세한 내용은 구글 개발자 도구 문서[링크]에서 확인하면 된다.)



sorce. http://slid.es/gruizdevilla/memory


ps.아마도 향후에는 타임라인쪽과 연동이 되지 않을까 생각한다.

Posted by 전용우
,

가끔씩 아래와 같이 polyfill형식으로 사용하는 경우가 있다.

(function(){
    var raf = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame|| window.msRequestAnimationFrame;
    var caf = window.cancelAnimationFrame || window.webkitCancelAnimationFrame|| window.mozCancelAnimationFrame|| window.msCancelAnimationFrame;

    if(raf&&!caf){
        var keyInfo = {};
        var oldraf = raf;
        raf = function(callback){
        function wrapCallback(){
            if(keyInfo[key]){
            callback();
            }
        }
        var key = oldraf(wrapCallback);
        keyInfo[key] = true;
        return key;
        }
        caf = function(key){
        delete keyInfo[key];
        }
        
    }else if(!(raf&&caf)){
        raf = function(callback) { return setTimeout(callback, 16); };
        caf = clearTimeout;
    }

    window.requestAnimationFrame = raf;
    window.cancelAnimationFrame = caf;
})();

이렇게 바로 실행해버리는 코드는 테스트할때 상황을 만들어야 하는데 상황을 제어하기가 어렵기 때문에 테스트가 어렵다.

예를들어, 난 아래와 같은 테스트케이스를 작성하려고 한다.


  1. requestAnimationFrame만 있는 경우
    1. cancelAnimationFrame을 호출했을 때 cancelAnimationFrame으로 정상적으로 멈추는지 테스트
  2. cancelAnimationFrame만 있는 경우
  3. requestAnimationFrame, cancelAnimationFrame 둘다 있는 경우
  4. requestAnimationFrame, cancelAnimationFrame 둘다 없는 경우
    1. cancelAnimationFrame을 호출했을 때 cancelAnimationFrame으로 정상적으로 멈추는지 테스트


(하위 목록(1-1. 4-1)에 대한 내용을 빼고 상위 목록만 얘기하자. 하위 목록에 대한 테스트를 얘기하면 길어지기 때문에 다음 기회에.)

근데 1,2,3,4의 상황이 브라우저마다 달라서 해당 로직이 정상적으로 실행되는지 테스트하려면 상황에 맞는 브라우저에서 테스트해야한다.


얼마나 귀찮고 복잡한 일인가?


어떻게 할까 고민하다가..

생각난 방법은 브라우저가 아니라 내가 직접 상황을 만들 수 있으면 가능하지 않을까 생각했다.(사실 이게 핵심인 것 같다. testability을 높이려면 개발자가 제어할 수 있는 환경(디자인..)을 만드는게 가장 중요한것 같다.)

그래서 생각한 방법은 바로 실행하는 것이 아니라 실행시점을 제어할것.

처음에는 아래와 같이 함수를 만들어서 처리했다.


function polyfillRequestAnimationFrame(){
    var raf = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame|| window.msRequestAnimationFrame;
    var caf = window.cancelAnimationFrame || window.webkitCancelAnimationFrame|| window.mozCancelAnimationFrame|| window.msCancelAnimationFrame;

    if(raf&&!caf){
        var keyInfo = {};
        var oldraf = raf;
        raf = function(callback){
        function wrapCallback(){
            if(keyInfo[key]){
            callback();
            }
        }
        var key = oldraf(wrapCallback);
        keyInfo[key] = true;
        return key;
        }
        caf = function(key){
        delete keyInfo[key];
        }
        
    }else if(!(raf&&caf)){
        raf = function(callback) { return setTimeout(callback, 16); };
        caf = clearTimeout;
    }

    window.requestAnimationFrame = raf;
    window.cancelAnimationFrame = caf;
}


추가로 window가 전역 객체라서 제어하게 되면 다른 곳에 영향을 줄지 몰라 아래와 같이 인자로 받아서 처리했다.(이 방법은 브라우저뿐만이 아니라 node.js와 같은 환경에서 테스트하기에 좋았다.)

function polyfillRequestAnimationFrame(global){
    var raf = global.requestAnimationFrame || global.webkitRequestAnimationFrame || global.mozRequestAnimationFrame|| global.msRequestAnimationFrame;
    var caf = global.cancelAnimationFrame || global.webkitCancelAnimationFrame|| global.mozCancelAnimationFrame|| global.msCancelAnimationFrame;

    if(raf&&!caf){
        var keyInfo = {};
        var oldraf = raf;
        raf = function(callback){
        function wrapCallback(){
            if(keyInfo[key]){
            callback();
            }
        }
        var key = oldraf(wrapCallback);
        keyInfo[key] = true;
        return key;
        }
        caf = function(key){
        delete keyInfo[key];
        }
        
    }else if(!(raf&&caf)){
        raf = function(callback) { return global.setTimeout(callback, 16); };
        caf = global.clearTimeout;
    }

    global.requestAnimationFrame = raf;
    global.cancelAnimationFrame = caf;
}


이렇게 만든후 난 아래와 같이 테스트케이스를 만들었다.


test("requestAnimationFrame만 있는 경우",function(){
    //Given
    var global = {
        "requestAnimationFrame":function(){}
    };

    var oldRequestAnimationFrame = global.requestAnimationFrame;
    
    //Then
    polyfillRequestAnimationFrame(global);

    //When
    notEqual(global.requestAnimationFrame,oldRequestAnimationFrame);
    equal(typeof global.cancelAnimationFrame,"function");

});

이후부터는 내가 직접 global에 적절한 상황을 만들어서 테스트할 수 있기 때문에 쉽게 테스트케이스를 작성할 수 있었다.

생각해보면 정말 단순하고 간단한 문제인데 처음에 고민을 많이 했다. -_-;


ps. 아쉬운건 requestAnimationFrame, cancelAnimationFrame이 둘다 없는 경운데 이땐 전에 쓴글(링크)처럼 clearTimeout이 정상적으로 변경되었다면 정상적이라고 판단했다.

Posted by 전용우
,

기존 자바스크립트 프로파일링은 어떤 함수가 얼마나 오래 걸렸는지 프로파일링 하기 때문에 소비 시간을 기준으로 느린 함수와 빠른 함수를 확인할 수 있었다.

하지만, 어떤 시점에 어떤 함수가 실행되고 얼마나 느린지 확인할 수 없었다.

예를 들어, 프로파일링을 실행하고 이벤트들을 수행한 다음 프로파일링을 끝내면 기존에는 프로파일링은 어떤 함수가 느린지 확인할 수 있었다면, 이벤트가 발생한 시점에 어떤 함수가 빠르고 느린지 확인할 수 없었다.

이 부분을 최근 크롬 개발자도구에서 프레임차트(Frame Chart)의 기능을 이용하여 시간의 흐름에 따라 함수의 실행시간을 확인할 수 있다.

즉, 특정 시점에 어떤 함수가 느린지 확인하고 싶을때 프레임 차트를 사용하면 유용하다.


프레임차트의 사용법은 기존과 같이 JavaScript CPU 프로파일 수집하기(Collect JavaScript CPU Profile)을 실행한 후 결과화면에서 아래와 같이 프레임차트(Frame chart)을 선택하면 된다.





아래와 같은 화면을 볼 수 있다.





여기서 확인할 수 있는 데이터는 5가지이다.


이름(Name) : 함수의 이름

실행 시간(Self time) : 호출된 함수를 제외하고 자신이 실행한 시간

전체 시간(Total time) : 함수가 실행된 전체 시간

실행 시간 합계(Aggregated self time) : 함수의 실행 시간(Self time)의 합

전체 시간 합계(Aggregated self time) : 함수의 전체 시간(Total time)의 합


이 5가지 데이터를 가지고 개선작업을 하면 된다.


ps. 크롬 개발자 도구는 정말 좋다.

Posted by 전용우
,

IE에서는 한 CSS파일에 4095개의 선택자까지만 적용되고 이후는 적용이 안된다.(링크)

그래서 네트워크비용을 줄이기 위해 CSS파일을 합치려면 최대 선택자가 4095개여야 한다.

넘는 경우에는 모두 합치고 CSS을 xhr로 가져와 적용하는 사례들도 있다.


근데 문제는 XHR은 크롬에서 리소스를 가져오는 순서중 뒤에 속한다.

(아마 타 브라우저도 비슷한것 같다.)

크롬에서 리소스를 내려받는 순서는 HTML -> CSS -> JS -> 이미지 순이다.

좀더 자세한 순서는 CachedResourceLoader.cpp(링크)에 보면 알 수 있다.


그래서 xhr은 JS을 받으면 실행하기 때문에 

link를 사용했더라면 html -> css ->  js였지만 xhr을 사용하게 되면 html -> js -> css로 되어 네트워크 비용이 감소할지 모르지만 사용자가 느끼는 체감 속도는 더 느릴 수 있다.


이런 경우는 걍 2개로 나누어서 받는게 더욱 효율적일 수 있다.

SPDY의 경우 적게는 4배에서 많게는 8배 정도 빠르다고 한다.(링크)

Posted by 전용우
,

파일 열기

크롬 개발자 도구를 열고(윈도우 F12, 맥킨토시 command + shift + i) Resource탭으로 이동후 ctrl + o을 누르면 원하는 파일을 열 수 있음. 



함수 찾기

열린 파일에서 ctrl + shift + o을 누르면 원하는 함수를 찾을 수 있음,



맥킨토시는 ctrl대신 command로 변경하면 됨.

source. https://vine.co/v/hzIpZLY5TpU

Posted by 전용우
,

최근(?) 자바스크립트에서는 모듈 패턴(Module Pattern)을 많이 사용한다.

이렇게 많이 사용하는 이유는 개인적으로 AMD의 영향이 크지 않을까 생각한다. (AMD의 좀 안좋은 인식이 있는데 이건 나중에 시간이 되면 쓰도록 하고....)

뭐.. 어쨌건 모듈 패턴을 사용하는 이유야 다양하겠지만 대부분은 지금의 자바스크립트에서 지원하지 않는 private 속성을 사용하기 위함이 큰 것 같다.(ECMA6에서는 뭐가 있는것 같은데..)

나는 사실 모듈 패턴을 선호하지 않는 편이다. 그 이유 중 하나가 코드 리딩이 힘들다.

왜냐하면 모듈 패턴은 함수로 감싼 형식이라 구현부를 볼 때 함수의 파라메터가 있다면 제일 마지막 부분을 확인해야 알 수 있고 클로져를 생각보다 많이 사용해 코드 리딩도 좀 힘들다. 그래서 난 개인적으로 모듈 패턴을 사용할 때는 파라메터를 넣지 않기를 선호하며 클로져도 적게 쓰려고 한다.

그리고 모듈 패턴을 쓰면 private의 사용이 많아지고 private이 많아질 수록 테스트는 만들기 힘들어진다. 물론, private를 테스트하기 힘든건 모듈 패턴의 문제는 아니다.

이런 경우 대체적으로 private함수는 직접 테스트하기 보다는 private함수를 사용하는 public함수를 테스트한다.

나도 보통은 그렇게 하는데 한편으론 맘에 안들때도 있다.

예를 들면 아래의 코드형식이다.

(function(){
    var global = this;
    function Stub(vName, sType){
        this.stubMethod = new StubMethod();
    }
    Stub.prototype.with_param = function(){
        return this.stubMethod;
    }

    //private
    function StubMethod(iStub){
    }
    StubMethod.prototype.and_return = function(vReturn){
    }

    global.stub = global.Stub = Stub;
})();

위에 코드를 보면 StubMethod는 외부에서 생성해서 사용하지 않기 때문에 외부에 노출하지 않는다.

그리고 with_param의 반환 값이 StubMethod인지 테스트해야 하는 코드를 아래와 같이 작성하려고 한다.

test("with_param의 반환 값은 StubMethod 인스턴스여야 한다.",function(){
    //Given
    var stubInstance = stub("Stub");
    //When
    var stubMethod = stubInstance.with_param();
    //Then
    ok(stubMethod instanceof StubMethod);
 });

근데 여기서 StubMethod는 외부에 노출하지 않았기 때문에 위와 같은 테스트가 불가능하다.

그래서 테스트하는 방법을 고민해봤는데 몇 가지가 떠올랐다.

첫 번째. 반환 값을 가지고 add_return을 사용하여 테스트한다.(간접적으로)

두 번째.  반환 값에서 add_return메서드가 있는지 확인하는 테스트를 작성해야 한다(덕타이핑과 같이)

세 번째. 키워드를 넘겨서 테스트 중이면 외부에서도 StubMethod를 접근할 수 있게 한다. (이건 좀 구린것 같다.)

처음 고민한건 add_return 함수의 기능을 테스트하면 StubMethod인지 동시에 테스트되는 방법이다. 물론 간접적으로 테스트 되지만 반환 값이 StubMethod인지 테스트하는 부분이 코드로 표현되지 않아서 좀 별루라고 생각했다.

이것 저것 고민하다가 결국엔 덕타이핑으로 판단했지만 여전히 찜찜함은 남는다.

test("with_param은 반환 값이 StubMethod 인스턴스여야 한다.",function(){
    //Given
    var stubInstance = stub("Stub");
    //When
    var stubMethod = stubInstance.with_param();
    //Then
    // ok(stubMethod instanceof StubMethod);
    // StubMethod는 private으로 접근할 수 없어 덕타이핑으로 판단한다.
    equal(typeof stubMethod.and_return,"function");
});


이와 관련한 질문도 많고 답변도 봤는데 나의 고민을 해결해주는 글은 없었다. 그리고 유사한 패턴이 jQuery의 Deferred, Promise인 것 같아서 코드와 테스트 케이스를 보니 위에 경우랑은 좀 차이가 있었다.

결론은 위와 같이 했지만 이런 경우는 어떻게 처리해야 할지 아직 고민이 된다.


ps. 여전히 고민인 건 정말 private로 만들어야만 했는가? 그냥 public이면 되는거 아닌가? 외부에 노출시켜 발생하는 문제에 비해 테스트의 가치가 큰게 아닌가? 등 많은 고민이 되는데 잘 모르겠다.


ps. 정말 오랫만에 블로그를 썼다. 한동안 바쁘다는 이유로 안썼는데 이젠 조금씩 써보려고 하는데 얼마나 갈지는 잘 모르겠다. ㅎㅎ

Posted by 전용우
,
먼저 JS Test Driver(JDT)의 지난 글 참조.

오늘은 JDT의 커버리지 사용기를 소개 할까 한다. 오픈은 10월 말경에 한것 같다.

JDT의 커버리지 툴은 JDT의 플러그인 형식으로 지원 하며 ANTLR을 이용해서 자바스크립트 문법을 분석하여 만들었다. (폴더가 plugins인걸로 봐서 다른 플러그인들도 개발할 예정인듯)

현재 지원 범위
  1. 향후 어떨지는 모르지만 현재 지원하는 커버리지는 라인커버리지만 지원
  2. 기존의 이클립스 플러그인에서 사용은 못하고 커멘드로만 사용가능(전에 방식을 봐서는 향후 지원할 듯)
  3. 결과는 콘솔로 파일당 라인 커버리지를 확인 가능하고 dat파일로 export할수 있으며 LCOV을 이용하여 비주얼 하게 볼수 있음.
  4. 일부 파일을 제외하는 기능 없이 모든 파일의 커버리지를 파일당 측정.(가능하다고는 메일링에 그러는데 안되는것 같음)

사용방법은 간단하다.
먼저 원글을 보면 나와있는데 간단히 알아 보면

전에 적은 JDT사용기 처럼 셋팅 후
  1. JsTestDriver.jar가 있는 폴더에 plugins 폴더를 만든다.
  2. coverage.jar를 다운 받아서 plugins 폴더에 넣는다.
  3. 기존에 만든 conf파일에 커버리지에 필요한 정보를 추가로 넣는다.(jar경로는 자신에 맞게 수정)
  4. 서버 띄우고 capture 한다.
  5. 기존과 똑같이 커멘드로 실행 시키며 된다.
위와 같이 진행을 하면 아래와 같은 결과를 받을수 있다.

사용자 삽입 이미지


위에 이미지에서 볼수 있듯이 test의 대한 결과를 보여주고 test가 100%일 때
하단에 각 파일에 대한 라인 커버리지율을 보여 주고 있다.

장점으로는
  1. test, coverage를 별다른 설치없이 한번에 볼 수 있다는 것은 정말 큰 장점이다.(IDE에선 성능을 볼수 있다. 전에 말한 JDT의 기능은 이제 거의 다 개발한듯.)
  2. 데이터를 export할수 있어 외부 툴하고도 연동하기 쉽게 되어 있다.(export한 데이터를 허드슨과 연동 하는 등)
단점으로는
  1. 향후 어떨지는 모르지만 아직 까지 파일 당 라인 커버리지만 지원하는건 큰 메리트가 없다.(적어도 함수당 라인 커버리지를 보여주거나 clover처럼 브랜치와 복잡도도 보여주면 좋을것 같다.구문 분석을 했기 때문에 항후 가능할것 같다.)
  2. 보기 힘든 정보.(향후 플러그인과 합쳐져서 비쥬얼 하게 보여 준다면 좋을것 같다)
  3. 느린 속도.(버그 인지 어쩔수 없는지 모르지만 여러 브라우져에서 테스트 할때 같은 js파일을 매번 분석해서 파일이 많아지면 굉장히 느릴것 같다)

결론은 아직 까지는 부족하지만 지금껏 해왔던 행보로는 충분히 좋아 질것 같다.
자바스크립트 커버리지툴은 내가 알기로 현재 JSCoverage 밖에 없는데 또 다른 커버리지 툴이 생겨서 좋다.

 

Posted by 전용우
,