Electron アプリを Travis CI でテストする


最近 Shiba という markdown プレビューアプリをつくったのですが，テストが無かったので追加してみました．

[Electron と Polymer と TypeScript でリッチなマークダウンプレビュアー Shiba つくった](http://rhysd.hatenablog.com/)

Electron アプリでテストを書いてるものは探した限りではあまりなくて，やり方もどこにも無かったので，今回は完全に我流でやってみました．Travis CI で CI するところまで含めて，そのメモ書き．実際に Shiba でテストを扱っているのは [この辺り](https://github.com/rhysd/Shiba/tree/055a11a0a2b4f727577fe61371a88d8db9277de5/tests) です．

## Electron アプリのテスト

npm を使って下記のようにテストを実行できるようにします．

```sh
$ npm test
```

`npm test` は node で JS のコードを実行するように `package.json` で設定し，そのスクリプトの中でテスト用の小さな Electron アプリを実行するようにします．

```javascript:scripts/run-test.js
var electron = require('electron-prebuilt');

// ...

var finished = child_process.spawnSync(
        electron,
        ['path/to/test-app', 'test-file1.js', 'test-file2.js'],
        {stdio: 'inherit'}
    );
process.exit(finished.status);
```

テストは Electron アプリ上で実行するため，Electron アプリの `stdio` を引き継ぐようにします．実行する Electron アプリはエントリポイントとなっている JS のソース内でテストスクリプトを実行し，結果を出力して終了します．実際に書いたテスト用 Electron アプリは [こんな感じ](https://github.com/rhysd/Shiba/tree/055a11a0a2b4f727577fe61371a88d8db9277de5/tests/runner) です．

今回は [mocha](https://mochajs.org/) と [chai](http://chaijs.com/) でテストを書いてみました．

### ブラウザプロセスのコードのテスト

ブラウザプロセスは iojs で実行されるので，下記のように，アプリの準備ができたらテストファイルをセットして実行すればコンソールに出力が出ます．

```javascript:test-app-main.js
app.on('ready', function() {
    let mocha = new Mocha();
    global.assert = chai.assert;

    // 引数で渡ってきたテストファイルを追加

    mocha.ui('bdd').run(function(failures) {
        process.on('exit', function(){ process.exit(failures); });
        app.quit();
    });
});
```

`app.quit()` 時の終了ステータスを直接指定する方法はありませんが，上記のようにしてステータスを指定できます．

### レンダラプロセスのコードのテスト

mocha と chai を使ってフロントエンドのテストコードを書きます．[ここに載っている方法](http://callmenick.com/post/basic-front-end-testing-with-mocha-chai) で，bower を使ってインストールし，`index.html` からそれを参照します．
僕はテストを TypeScript で書いていて，レンダラプロセスのテストコードが1つのファイルになっているので，単純に `index.html` でそれを `<script>` タグで読み込んでテストを実行します．
テストを実行するためのウィンドウはテスト用の Electron アプリの中で `BrowserWindow` オブジェクトをつくって `loadUrl()` で読み込みます（[実際にやっている箇所はここ](https://github.com/rhysd/Shiba/blob/055a11a0a2b4f727577fe61371a88d8db9277de5/tests/runner/main.js#L46)）．結果はウィンドウ内の `<div id="mocha"></div>` に表示されます．ここでは，ウィンドウが閉じた時に `app.quit()` が呼ばれるようにしておきます．

ファイルが複数あったり，動的に読み込むファイルを変えたい場合はブラウザプロセスから ipc 経由で渡して動的に `<script>` で読み込むか，`BrowserWindow.webContents().executeJavaScript()` で読み込ませるのが良さそうです．
また，テストの結果をブラウザプロセス側に返したければ，その場合も ipc 越しに渡す必要がありそうです．


## Travis CI でのテスト

Electron アプリを Travis で動かすのは少し面倒でした．単純に Travis 上で Electron アプリを実行しようとすると次のようなエラーログが出てしまいます [^1]．

```
Gtk: cannot open display: 
```

Electron（というか Chromium）はヘッドレスモードのようなディスプレイを開かないモードが無く，[今後も実装しない](https://github.com/atom/electron/issues/228)ようなので，ここをどうにかしないといけません．
一般的にブラウザのテストをディスプレイを開けないような環境でどうやっているのかを調べてみると [Xvfb](http://www.x.org/archive/X11R7.6/doc/man/man1/Xvfb.1.xhtml) を使って仮想的にディスプレイを開くことで解決しているようです．今回はそれを使います．

### `.travis.yml` を書く

```yaml
sudo: false
```

直接は関係ないですが，`sudo` を `false` にしておくと[新しいコンテナベースのワーカ](http://docs.travis-ci.com/user/migrating-from-legacy/) を使うことができ，CI の立ち上がりが速くなります．
`sudo` が使えなくなるので，`apt` によるインストールは `addon` を使います．ここでは Xvfb をインストールします．

```yaml
addons:
  apt:
    packages:
      - xvfb
```

次に，`install` フェーズで Xvfb を立ち上げて仮想ディスプレイをつくっておきます．

```yaml
install:
  - export DISPLAY=':99.0'
  - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
```

Electron は `$DISPLAY` を見ているようで，`$DISPLAY` を設定しておかないと動きません．画面サイズは都合の良いものに適宜調整すると良さそうです．これで，あとは最初に書いておいたテストスクリプトを実行すると無事 Electron が動き，その上でテストスクリプトが走ります．

```yaml
script:
  - npm test
```

今回書いた `.travis.yml` は[こんな感じ](https://github.com/rhysd/Shiba/blob/055a11a0a2b4f727577fe61371a88d8db9277de5/.travis.yml)です．

また，Travis CI 上でのテストログは[こんな感じ](https://travis-ci.org/rhysd/Shiba/builds/74407689) になりました．めでたし．


[^1]: https://twitter.com/Linda_pp/status/629185292902117376