[go: up one dir, main page]
More Web Proxy on the site http://driver.im/

けんごのお屋敷

2014-07-22

Chrome Extension の CI が出来るようになるまで(準備編)

個人的に趣味で Chrome Extension の開発をしていますが、最近いろいろとノウハウも溜まってきたので Chrome Extension の CI について少しまとめてみるシリーズです。

前回の記事はこちら。

Chrome Extension の CI が出来るようになるまで(導入編)

目次

  1. はじめに
  2. Chrome Extension のテストを書く
  3. Chrome Extension のテストを実行する
  4. Chrome Extension を CI する
    • CI サービス Wercker
    • Wercker にリポジトリを登録する
    • wercker.yml
    • Step の作り方
    • バッヂ

Chrome Extension のテストを実行する

Chrome Extension でテストを書くことにも慣れてくると、次はそのテストの実行をなんとか簡略化したくなってきます。前回も説明したように SpecRunner.html でテストを実行することも出来ますが、もっと良い方法がありますので、ここではそれについて紹介していきたいと思います。

テストランナー Karma

Jasmine を始めたての時は誰もが SpecRunner.html でテストを実行していくと思いますが SpecRunner.html での実行はいくつかのメリット・デメリットがあります。

メリット

  • デフォルトで入ってるし何も言わずに導入できる
  • ブラウザで開くだけなので実行が簡単
  • HTML で整形して結果を出力してくれるので見やすい

デメリット

  • テスト規模が大きくなってくると縦スクロールが長くなってくる
  • テストを更新する度にブラウザも更新しないといけない
  • CI するには適していない

メリットは一言でいうと簡単で使いやすいというところに尽きると思いますが Jasmine になれてくるとだんだんそのメリットの価値が下がってきます。そこで、慣れてきた頃にデメリットを解消するために、テスト実行を SpecRunner.html 以外のツールにまかせてみます。それが Hometype で使っている Karma です。

Karma は Google が開発した JavaScript のテストランナー(テスト実行用のツール)で、以下のような特徴があります。

  • コマンドラインからテストを実行できる
  • ファイルの変更を監視して自動的にテストを再実行できる
  • プリプロセッサの指定でコードカバレッジを出力することができる
  • テストの実行に利用するブラウザを切り替えることが出来る

プリプロセッサやブラウザについてはプラガブルになっており、いろいろなものを指定できます。プリプロセッサであればコードカバレッジ出力を一例として、CoffeeScriptの変換をしてくれるものや、AngularJSのテンプレートをJavaScriptに変換するもの、他にも有志が作った多くのプリプロセッサがあります。ブラウザについても Chrome はもちろんのこと FireFox や Safari、Opera、そして PhantomJS などがカバーされています。試したことはありませんがマニュアルには IE にも対応していると書かれています。

Karma のインストール

Karma をインストールしてみましょう。Karma は Node.js 製なので、まずは Node.js のインストールが必要です。Mac を使っているのであれば Homebrew を使って一発でインストールできます。また、今回はテスト実行に PhantomJS を使いますので、そのインストールも一緒にやっておきます。

$ brew install node phantomjs

Node.js については、最近は nodebrew という Node.js のバージョン管理ツール(rbenv や phpenv、plenv のようなもの)があるようですのでこの辺を参考に nodebrew を使ってインストールしてもいいかもですね。

Node.js のインストールが終われば次に Karma のインストールです。以下のコマンドで1発でインストールできます。

$ cd path/to/extension
$ npm install karma karma-jasmine@0.2.2 karma-coverage karma-phantomjs-launcher --save-dev

インストールするものは Karma 本体に加えて Karma で Jasmine を使うためのアダプタ、コードカバレッジを出力するためのプリプロセッサプラグイン、PhantomJS でテストを実行するためのプラグインをそれぞれ一緒にインストールしておきます。これらのパッケージは、カレントディレクトリに node_modules というディレクトリが作られて、そこ以下にインストールされます。なお node_modules ディレクトリについては .gitignore で無視してよいと思います。

[kengo@tkengo-mac] $ ls node_modules
karma/  karma-coverage/  karma-jasmine/  karma-phantomjs-launcher/

パッケージをインストールする際に最後に --save-dev をつけると、自動的に package.json を作ってくれてその中に以下の様な依存関係を書き出してくれます。

{
  "devDependencies": {
    "karma-jasmine": "^0.2.2",
    "karma-coverage": "^0.2.1",
    "karma-phantomjs-launcher": "^0.1.4",
    "karma": "^0.12.3"
  }
}

このファイルをリポジトリに含めておけば、パッケージをインストールする際にパッケージ名を指定しなくても

$ npm install

とだけ書けば package.json の依存関係を参照してパッケージをインストールしてくれるようになります。

Karma でテストを実行する

Karma がインストールできたら早速 Karma を使ってテストを実行してみましょう。ただ、その前に Karma を実行するためには設定ファイルが必要になるため、設定ファイルを準備します。ひな形を準備してくれるコマンドがあるので実行してみましょう。karma init コマンドを実行すると対話的に設定ファイルのひな形を作ることが出来ます。

$ ./node_modules/karma/bin/karma init

# Karma で利用するテストフレームワーク
Which testing framework do you want to use ?
Press tab to list possible options. Enter to move to the next question.
> jasmine

# Require.js を使うかどうか
Do you want to use Require.js ?
This will add Require.js plugin.
Press tab to list possible options. Enter to move to the next question.
> no

# どのブラウザを使ってテストするのか
Do you want to capture any browsers automatically ?
Press tab to list possible options. Enter empty string to move to the next question.
>

# テスト実行に必要な JavaScript ファイルのパス
What is the location of your source and test files ?
You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js".
Enter empty string to move to the next question.
>

# テスト実行に不必要な、読み込みたくないファイルのパス
Should any of the files included by the previous patterns be excluded ?
You can use glob patterns, eg. "**/*.swp".
Enter empty string to move to the next question.
>

# ファイルの変更を監視して自動的にテストを再実行するかどうか
Do you want Karma to watch all the files and run the tests on change ?
Press tab to list possible options.
> yes

Config file generated at "karma.conf.js".

という風に質問に答えていけば karma.conf.js にひな形が出来上がります。途中で空でエンターを押して進んだ箇所については、直接そのファイルを編集します。

module.exports = function(config) {
  config.set({
    // ......
    // 省略
    // ......

    // テストに必要なファイルの読み込み
    files: [
      "lib/jquery-2.0.1.min.js",
      'spec/lib/jasmine-jquery.js',
      'spec/lib/mocking.js',
      "js/executer.js",
      "spec/js/**/*.js"
    ],
    exclude: [ ],

    // ......
    // 省略
    // ......

    // テスト実行に PhantomJS を使う
    browsers: ['PhantomJS'],

    // テストを実行したら Karma を終了させる。
    // Jenkins や Travis、Wercker などを使って CI する際は
    // Karma が実行されっぱなしだと処理が次に進まないので
    // テストが終わったら Karma も終了させるようにします。
    singleRun: true,
  });
};

これでテスト実行の準備は完了です。以下のコマンドを実行してテストを走らせてみましょう。

$ ./node_modules/karma/bin/karma start
INFO [karma]: Karma v0.12.17 server started at http://localhost:9876/
INFO [launcher]: Starting browser PhantomJS
INFO [PhantomJS 1.9.7 (Mac OS X)]: Connected on socket zJcVpRem8HOkR0GkcaH9 with id 34963439
PhantomJS 1.9.7 (Mac OS X): Executed 2 of 2 SUCCESS (0.074 secs / 0.083 secs)

テスト実行が成功しました。

ファイルの変更を監視する

Karma でテスト実行が出来るようになりましたが、今の所はファイル変更の度に

$ ./node_modules/karma/bin/karma start

を実行しなきゃいけないので、次は --no-single-run オプションをつけて常時起動にしておきます。そしてファイルの変更を監視する設定はデフォルトで yes になっているので、あとは何もすることはありません。

$ ./node_modules/karma/bin/karma start --no-single-run
INFO [karma]: Karma v0.12.17 server started at http://localhost:9876/
INFO [launcher]: Starting browser PhantomJS
INFO [PhantomJS 1.9.7 (Mac OS X)]: Connected on socket 9S8jNMSy-SplLao8eDvW with id 8548867
PhantomJS 1.9.7 (Mac OS X): Executed 2 of 2 SUCCESS (0.034 secs / 0.073 secs)
INFO [watcher]: Changed file "js/executer.js".
PhantomJS 1.9.7 (Mac OS X): Executed 2 of 2 SUCCESS (0.124 secs / 0.102 secs)
INFO [watcher]: Changed file "spec/js/executer_spec.js".
PhantomJS 1.9.7 (Mac OS X): Executed 2 of 2 SUCCESS (0.024 secs / 0.042 secs)

INFO [watcher] でファイルの変更を検知して、その次の行でテストが再実行されているのがわかります。

コードカバレッジを出力する

Karma を使ってコードカバレッジを出力してみます。karma.conf.js に次のような設定を追記してます。

preprocessors: {
 'js/**/*.js': [ 'coverage' ]
},
reporters: [
  'progress',
  'coverage'
],
coverageReporter: {
  type: 'html',
  dir : 'coverage/'
},

そして Karma を再起動します。C-c で終了させて、もう一度起動させれば OK です。テストが実行されると karma.conf.js と同じ場所に coverage/ というディレクトリができていると思います。その中に、テスト実行に利用したブラウザ毎のディレクトリがあって、さらにその中にカバレッジレポートの出力があります。

$ ls coverage
PhantomJS 1.9.7 (Mac OS X)/
$ ls coverage/PhantomJS\ 1.9.7\ \(Mac\ OS\ X\)
index.html  js/  prettify.css  prettify.js

coverage/PhantomJS 1.9.7 (Mac OS X)/index.html を開いてみるとカバレッジを見ることが出来ます。

これは Hometype のカバレッジです。赤い。

HTML Fixture を読み込む

Karma は自前で Web サーバーを立ち上げており、そこに対してアクセスしてテストを実行しています。Karma 起動時に INFO [karma]: Karma v0.12.17 server started at http://localhost:9876/ というログが表示されている通り、私達も http://localhost:9876 にアクセスすれば画面を表示することができます。

前回 HTML Fixture を読み込む で、テスト用に Ajax で HTML Fixture を読み込むという処理がありましたが Karma でこれを実行する際には http://localhost:9876 に対して Ajax のアクセスがいくので Karma のサーバーで HTML Fixture のファイルをホストしてあげる必要があります。

幸いこれは以下のように設定を追加するだけでアクセスできるようになります。

files: [
  "lib/jquery-2.0.1.min.js",
  'spec/lib/jasmine-jquery.js',
  'spec/lib/mocking.js',
  "js/executer.js",
  "spec/js/**/*.js",
  // spec/fixtures 以下の .html ファイルを読み込む
  // watched: true   はファイルの変更を監視してテストの再実行を行う
  // included: false はテスト実行時にファイルを読み込まない
  // served: true    は Karma 自前サーバーからアクセスできるようにする
  {
    pattern: 'spec/fixtures/**/*.html',
    watched: true,
    included: false,
    served: true
  }
]

これで、たとえば前回の spec/fixtures/hoge_fixture.html については http://localhost:9876/base/spec/fixtures/spec/fixtures/hoge_fixture.html でアクセスできるようになります。ただし URL を見てもらってわかる通り Karma がホストするファイルには base/ の下にフィクスチャのパスが来ています。なので、テストを1箇所だけ、フィクスチャを読み込むパスを設定しているところを修正する必要があります。

describe('dom fixture', function() {
  var element;

  beforeEach(function() {
    // フィクスチャが配置されているディレクトリを指定
-   jasmine.getFixtures().fixturesPath = 'fixtures';
+   jasmine.getFixtures().fixturesPath = 'base/spec/fixtures';
    // フィクスチャを読み込む
    loadFixture('hoge_fixture.html');

    // テスト対象のメソッドを呼び出す
    fuga('dom fixture test');
    element = document.getElementById('hoge');
  });

  it('should have a text', function() {
    expect(element.innerText).toBe('dom fixture test');
  });
});

今回も長くなってきたので続きは次回。