가끔씩 아래와 같이 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 전용우
,