Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

Sauce Labs으로 유닛테스트 자동화하기

몇일 전에 Sauce Labs에 대해서 소개했는데 크로스 브라우징 테스트환경을 일일이 구축하는 것보다는 훨신 편하지만 그래도 역시 크로스 브라우징 테스트는 피곤하고 노력이 많이 드는 일이다. 그래서 보통은 유닛 테스트를 작성해서 반복되는 테스트에 대한 비용을 크게 줄일 수 있다. 자바스크립트는 보통 UI가 관여되어 있기 때문에 테스트를 작성하기가 어렵기도 하지만(작성하더라도 쉽게 깨지기도 하고) 인프라 등 여러 모로 서버에 비해서는 아직은 부족한 부분이 좀 있어 보이기도 한다. 어쨌든 자바스크립트 유닛 테스트를 작성할 수 있는 라이브러리가 여러가지가 있는데 요즘은 Jasmine을 많이 쓰는 것 같고 jQuery가 써서 유명한 QUnit나 내가 좋아하는 Mocha(Node.js용이지만 브라우저에서도 쓸 수 있다.)등이 있다.

유닛 테스트 작성

유닛 테스트를 작성하는 방법은 각 테스트 프레임워크에 잘 나와있고 이 글의 범위를 넘어서므로 예제를 위해서 간단히 작성한 테스트만 보자.

<!-- public/test/qunit-test.html -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>QUnit Example</title>
    <link rel="stylesheet" href="../components/qunit/qunit/qunit.css">
  </head>
  <body>
    <div id="qunit"></div>
    <div id="qunit-fixture">
      <div class="example">
      </div>
    </div>
    <script src="../components/qunit/qunit/qunit.js"></script>
    <script>
      test( "hello test", function() {
        var results = document.querySelectorAll('.example');
        equal(results.length, 1, "Passed!");
      });
    </script>
  </body>
</html>

QUnit으로 작성한 간단한 테스트로 querySelectorAll()의 동작 여부를 확인하는 테스트다.


<html>
  <head>
    <meta charset="utf-8">
    <title>Mocha Tests</title>
    <link rel="stylesheet" href="../components/mocha/mocha.css" />
  </head>
  <body>
    <div id="mocha"></div>
    <script src="../components/expect/expect.js"></script>
    <script src="../components/mocha/mocha.js"></script>
    <script>mocha.setup('bdd')</script>
    <script>
      describe('Test Suite', function() {
        it('hello test', function() {
          expect(Object.keys).to.be.ok();
        });
      });

      mocha.run();
    </script>
  </body>
</html>

이번엔 Mocha로 작성한 테스트로 Object.keys의 지원여부를 확인하는 테스트다. 이렇게 작성한 테스트 페이지를 웹브라우저에서 확인해 보면 다음과 같은 테스트 결과 화면을 볼 수 있다.

QUnit 테스트를 실행한 화면

Mocha 테스트를 실행한 화면

UI와 관련해서는 모든 테스트를 유닛테스트로 작성하긴 어렵지만 최대한 유닛테스트를 작성해 놓으면 수동 테스트에 대한 비용을 많이 줄일 수 있다.

Sauce Labs와 Grunt 연동

위에서 작성한 테스트를 Sauce Labs에서 브라우저별로 띄워볼 수도 있겠지만 귀찮기는 매한가지다. 다행히도 Sauce Labs는 이러한 테스트 프레임워크에 대한 자동화도 지원하고 있다.(BrowserStack은 Selenium만 지원하고 아직 테스트 프레임웍은 아직 지원하지 않는다.) Sauce Labs는 Jasmine, Qunit, Mocha, YUI를 현재 지원하고 있고 가이드 문서에 사용방법이 잘 나와있는데 요즘 프론트앤드 빌드도구의 대세인 Grunt를 중심으로 설명하고 있다.

Grunt와 Sauce Labs를 연동하려면 grunt-saucelabs를 의존성에 추가해야 하고 다음과 같이 Gruntfile.js를 작성한다.(이 예제에서는 grunt-contrib-connect도 필요하다.)

module.exports = function(grunt) {
  'use strict';

  var browsers = [{
    browserName: "chrome",
    version: "27",
    platform: "windows 8"
  }, {
    browserName: "internet explorer",
    version: "9",
    platform: "windows 7"
  }, {
    browserName: "internet explorer",
    version: "8",
    platform: "windows 7"
  }, {
    browserName: "internet explorer",
    version: "7",
    platform: "XP"
  }, {
    browserName: "firefox",
    version: "21",
    platform: "os x 10.6"
  }, {
    browserName: "firefox",
    version: "12",
    platform: "linux"
  }, {
    browserName: "iphone",
    version: "6",
    platform: "os x 10.8"
  }];

  // Project configuration.
  grunt.initConfig({
    connect: {
      server: {
        options: {
          base: "public",
          port: 9999
        }
      }
    },
    'saucelabs-qunit': {
      all: {
        options: {
          username: 'ID',
          key: 'ACCESS-KEY',
          urls: ["http://localhost:9999/test/qunit-test.html"],
          tunnelTimeout: 5,
          concurrency: 2,
          build: 'test-build',
          browsers: browsers,
          testname: "qunit tests",
          tags: ["master"]
        }
      }
    },
    'saucelabs-mocha': {
      all: {
        options: {
          username: 'ID',
          key: 'ACCESS-KEY',
          urls: ["http://localhost:9999/test/mocha-test.html"],
          tunnelTimeout: 5,
          concurrency: 2,
          build: 'test-build',
          browsers: browsers,
          testname: "mocha tests",
          tags: ["master"]
        }
      }
    },
  });

  // These plugins provide necessary tasks.
  for (var key in grunt.file.readJSON("package.json").devDependencies) {
    if (key !== "grunt" && key.indexOf("grunt") === 0) grunt.loadNpmTasks(key);
  }

  // Default task.
  grunt.registerTask('test', ['connect', 'saucelabs-qunit', 'saucelabs-mocha']);
};

파일이 꽤 긴데 Grunt를 사용해 보았다면 아주 복잡하지는 않다. 상단에서 browsers라는 변수에 테스트 대상이 될 브라우저 목록을 지정했다. 플랫폼과 브라우저 명이나 지원 버전은 Sauce Labs 문서에 Selenium 부분에 나와있는 문자열을 참고해서 작성하면 된다.(정확히 언급되어 있지 않아서 좀 헷갈리는데 테스트 해보면서 없는 브라우저라고 나오면 수정해가면서 테스트하면 된다.) 프로젝트의 테스트 대상이 되는 브라우저 목록을 모두 적어주면 된다.

그리고 Grunt 태스크로 먼저 connect를 정의했다. connect는 페이제에 접속할 정적서버를 띄워주는 용도이고 웹서버는 9999포트로 띄웠다. 테스트파일을 그냥 파일시스템에서 접속해도 되긴 하지만 결국은 웹이므로 정적서버를 띄워서 웹주소로 접속하는게 더 좋다고 생각한다. 이어서 saucelabs-qunitsaucelabs-mocha를 정의했는데 앞에서 작성한 QUnit 테스트와 Mocha 테스트를 모두 테스트하기 위함이다. 이 두 태스크는 grunt-saucelabs에서 지원하는 태스크다. 태스크에서는 Sauce Labs 접속을 위한 아이디와 Access key를 입력하고 urls에 테스트할 페이지 주소목록을 지정한다. build는 Travis-CI를 사용한다면 환경변수의 Job 아이디를 사용하면 되지만 여기서는 임의의 값을 사용했고 browsers에는 앞에서 정의한 브라우저 목록을 지정했다. testnametags는 테스트를 구분해 주기위한 이름을 작성하면 된다. 마지막 부분에서는 test라는 태스크에 connect, saucelabs-qunit, saucelabs-mocha를 지정했다. 즉 test 태스크를 실행하면 connect로 웹서버를 띄운 뒤 saucelabs-qunit, saucelabs-mocha를 순차적으로 실행한다.

크로스 브라우징 테스트 자동화

이제 Grunt 설정도 끝났으므로 실행을 해보자. 앞에서 지정한대로 터미널에서 grunt test --force로 실행할 수 있고 --force옵션을 사용한 이유는 grunt가 중간에 테스트가 실패하면 멈추게 되는데 크로스 브라우징 테스트이므로 모든 테스트를 다 실행하기 위해서이다.

grunt test를 실행해서 Sauce Labs에 Qunit이 실행되는 화면

Sauce Labs의 터널링을 연결하고 Qunit 테스트를 브라우저별로 하나씩 실행하고 있다. 콘솔 메시지는 별로 친절하지 않은데 어떤 브라우저에 테스트하고 있는지 콘솔 메시지에는 나타나지 않는다.(이건 많이 안좋다.) 그리고 테스트마다 테스트의 실행결과가 함께 출력되고 있다.

이어서 Sauce Labs에 Mocah가 실행되는 화면

QUnit 테스트가 끝나면 다시 터널링을 연결하고 Mocha 테스트를 실행한다. 마찬가지로 콘솔 메세지가 친절하지 않은데 여기서는 실패한 테스트가 있는 경우에만 결과가 나오는 것 같다.

자세한 결과는 Sauce Labs 페이지에서 확인하는 것이 더 낫다.

grunt로 실행한 테스트 결과가 대시보드에 나타난 화면

지정한 빌드명의 테스트 결과를 볼 수 있고 어떤 플랫폼의 브라우저에서 테스트가 실패했는지 확인할 수 있다. 이 목록에서 봤을 때 QUnit 테스트(querySelectorAll 지원여부)는 IE7에서 실패하고 Mocha 테스트(Object.keys 지원여부)는 IE 7,8,9에서 모두 실패하는 걸 볼 수 있다.

브라우저별 테스트 성공/실패 여부가 요약되서 나오는 화면

빌드명에 마우스 커서를 올리면 위처럼 현재 테스트가(전체 합쳐서) 어떤 브라우저에서 실패했는지를 한눈에 볼 수 있다. 이제 유닛테스트만 잘 작성해 놓으면 터미널의 명령어 하나로도 다수의 브라우저에서 지원여부를 한번에 테스트할 수 있다. 그리고 이벤트 기간같은건지 모르겠지만 처음 자동화 테스트를 실행하면 축하한다면 50분을 더주고 자동화 테스트를 50번 더 실행하면 1,000분을 추가로 또 준다고 한다. 자동화 테스트는 시간이 별로 안걸리므로 무료로도 상당기간은 이용해 볼 수 있다.

테스트 결과 상태 이미지

Github와 Travis CI덕에 요즘은 프로젝트화면에 빌드상태, 테스트 상태등을 표시하는게 유행인데 Sauce Labs도 테스트한 결과를 보여주는 이미지를 제공하고 있다.(약간 테스트한 결과로는 빌드명을 지정하지 않으면 이미지가 안뜨는것 같은데 그렇다고 빌드명 별로 따로 지정해서 상태 이미지를 볼 수 있는 건 아닌 것 같다.) 간단히 성공/실패를 보여주는 이미지는 https://saucelabs.com/buildstatus/ID로 사용할 수 있고 브라우저별로 보여주는 매트릭스 이미지는 https://saucelabs.com/browser-matrix/ID.svg로 이용할 수 있다.(문서에 나와있듯이 인증이 필요한 계정은 hmac 키를 함께 전달해야 한다.)

테스트 결과 매트릭스 이미지

CI나 프로젝트 같은 곳에 위 이미지를 추가해 놓으면 프로젝트의 브라우저 지원상태를 한눈에 파악할 수 있다.

여러모로 찾아봤지만 빌드별로(여기서는 전체 빌드에 하나의 이름이 지정되브로) 변화과정을 볼 수 있도록 상태이미지같은 건 제공하지 않고 최종 상태만 보여주고 있는 것 같다. 각 빌드과정의 변화과정을 파악하는 것도 중요한데 이는 좀 아쉬운 부분이다. 실제 적용한다고 치면 내가 바라는 최종 상태는 TestSwarm처럼 빌드별로 보여주는 것인데(jQuery의 TestSwam 참고) Sauce Labs으로는 자동화는 할 수 있지만 아직 이러한 연동은 안되는 것으로 보인다.(API를 사용해서 구축하면 될 것도 같지만...) 현재 TestSwarm이 BrowserStack은 지원하고 있지만 Sauce Labs은 지원하지 않고 있다.(프로젝트는 만들어져있지만 비어있다.)

2013/07/27 03:03 2013/07/27 03:03