イマドキのJSテスト - karma + karma-html2js-preprocessorでブラウザ/DOMを絡めたUIテストを実行する編 〜 JSおくのほそ道 #031

こんにちは、ほそ道です。

[前回のJasmineモジュールテスト](http://qiita.com/setzz/items/4e508e804b0e50e7b6ec)に引き続きテストを掘り下げていきます。
今回はUIテストをやってみたいと思います。前回分も読んでおいていただくと理解度シナジーが起こって良い感じになると思います。

[目次はこちら](http://qiita.com/setzz/items/ad9a9c99a4fda041a563)

# 今回扱う範囲/扱わない範囲
- まずはブラウザ上でJavaScriptを実行しJasmineと絡めて実行結果をテストします。
- 次にブラウザ上のDOMの状態(スタイル含む)をテストします。
- 今回のテスト内容にとりあえず遷移というかページ(リ)ロードは含めません。これが絡むと非常に厄介さが増すので遷移は別途取り上げたいと思います。

# Karmaを実行する
まずはKarmaの実行から。
Karmaは実ブラウザでJavaScriptコードを実行できる実行環境で、テストフレームワークと組み合わせてテストを行う事ができます。
エミュレータやヘッドレスブラウザではなく実ブラウザというところがミソです。ただPhantomJSの起動にも対応しています。
ブラウザはPCにインストールされている必要があるのでMacで気軽にIE立ち上げたりはできません。

## インストール

それではプロジェクトディレクトリを作っていつものインストールから始めましょう。

```bash:コンソール
npm init
npm i -D karma karma-jasmine karma-chrome-launcher karma-firefox-launcher
```

これで`karma --version`コマンドが通ればインストールは問題ありません。
うまく実行できない場合は別途下記のコマンドを叩いてCLIをグローバルインストールしましょう。

```bash:コンソール
npm i -Dg karma-cli  
```

## conf.js

今回も被テストファイルは`src`ディレクトリに、テストファイルは`spec`ディレクトリに設置します。
上記ディレクトリが作成できたらKarmaの設定ファイルを出力しましょう。
下記のコマンドをたたきます。

```bash:コンソール
karma init karma.conf.js  
```

下記のように質問が出てくるので答えましょう。終わると`karma.conf.js`が出力されます。

```
Which testing framework do you want to use ?
Press tab to list possible options. Enter to move to the next question.
> jasmine

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.
> Chrome
> Firefox
> 

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.
> src/*.js
> spec/*.js
> 

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

```

設定ファイルは手で作っても構いません。下記のようになります。

```js:karma.conf.js
module.exports = function(config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine'],
    files: ['src/*.js', 'spec/*.js'],
    exclude: [],
    preprocessors: {},
    reporters: ['progress'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome', 'Firefox'],
    singleRun: false
  });
};

```

## テストの実行
それでは簡単なJSファイルを設置してテストしてみましょう。
ここまでの構成は下記のようになります。

```text:プロジェクト構成
.
├── karma.conf.js
├── node_modules
├── package.json
├── spec
│   └── sampleSpec.js
└── src
    └── sample.js
```

- まずは被テストファイルから

```js:src/sample.js
function add(x, y) {
  return x + y;
}
```

- 次にテストファイルです

```js:spec/sampleSpec.js
describe("function-add", function() {
  it("２つの引数の和を返すべし", function() {
    expect(add(10, 20)).toBe(30);
  });
});
```

- package.jsonにテストコマンドを記述しましょう。

```json:package.json
{
  …
  "scripts": {
    "test": "karma start karma.conf.js"
  },
  …
}
```

- 実行しましょう

```bash:コンソール
npm test
```

- 実行結果  
![スクリーンショット 2015-05-30 19.09.52.png](https://qiita-image-store.s3.amazonaws.com/0/39330/9566e3ec-2f68-68bc-838f-93a8046cb89e.png)

ポコポコっとChromeとFirefoxのブラウザが起動してきて、`TOTAL: 2 SUCCESS`の表示が出れば実行・テスト成功です。
テストコードはブラウザ上で実行されたことになります。

- テストファイルが`require`してないことにも注目です。コンフィグファイルの`files: ['src/*.js', 'spec/*.js']`の設定は自動的に相互参照を可能とします。
- コンフィグファイルの`autoWatch: true`と`singleRun: false`の設定が効いていてファイルの内容を書き換えてセーブすると自動的にもう一回テストが流れるようになっています。

# これじゃあただのjasmine-nodeモジュールテストとと変わらない？
と、最初は思っちゃいました。
ただ、この時点でもほそ道的には下記２点メリットはあると思うんですが、どうですかねー？

- **ブラウザごとにで使用不能なメソッドの呼び出しを行っていないかテストできる**
- **isomorphicなJSモジュールになっているかテストできる**

# HTMLを絡めたテスト

## 環境準備

さて、HTMLを絡めましょう。わしゃあブラウザでテスト走らせるならHTMLテストがしたいんじゃー！！
先ほどのプロジェクトにkarma-html2js-preprocessorを追加インストールしましょう。

```bash:コンソール
npm i -D karma-html2js-preprocessor
```

つづいて`karma.conf.js`に設定を追加します。
htmlファイルも実行対象に入れる。
htmlファイルにはkarma-html2js-preprocessorをカマす。
という設定です。

```js:karma.conf.js
module.exports = function (config) {
  config.set({
    …
    files: [
      '*.html',
      'src/*.js',
      'spec/*.js'
    ],
    …
    preprocessors: {
      '*.html': 'html2js'
    },
    …
  });
};
```

## WEB画面の構成

次に`index.html`, `src/appendLi.js`, `spec/appendLiSpec.js`の３ファイルを追加します。
さっき使った`src/sample.js`と`spec/sampleSpec.js`は消しちゃって構いません。
プロジェクト構成は下記のようになります。

```text:プロジェクト構成
.
├──index.html
├── karma.conf.js
├── node_modules
├── package.json
├── spec
│   └── appendLiSpec.js
└── src
    └── appendLi.js
```

それでは各ファイルの中身を見ていきましょう。
まずは`src/appendLi.js`ファイルからです。
`#list`なul要素にli要素を追加します。

```js:src/appendLi.js
function appendLi() {
  var ul = document.getElementById('list'),
      li = document.createElement('li');
  li.innerHTML = 'karma!';
  li.style.fontSize = '20px';
  ul.appendChild(li);
}
```

次に`index.html`です。body部だけ。
`src/appendLi.js`を参照していて、クリックされるとappendLi関数をコールします。

```html
<body>
<input id="btn" type="button" value="click!" onclick="appendLi()">
<ul id="list"></ul>
<script src="src/appendLi.js"></script>
</body>
```

この時点でも下記のようにブラウザで動作が確認できます。

![スクリーンショット 2015-05-30 22.09.22.png](https://qiita-image-store.s3.amazonaws.com/0/39330/8a031c11-fda7-b3d8-dd5e-b06a62068314.png)


## テストコード

さて、テストコードとなる`spec/appendLi.js`です。
ボタンをクリックして期待通りの動作をするか。
ポイントは`beforeEach`内でHTMLの初期化をしているところです。
karma-html2js-preprocessorを使うと
`window.__html__['index.html'];`のような記述でhtmlファイルをJavaScriptで扱える文字列として呼び出せます。

```js:spec/appendLi.js
describe("function-appendLi", function() {

  beforeEach(function() {
    document.body.innerHTML = window.__html__['index.html'];
  });

  it("追加されたli要素のテキストは'karma!'となるべし", function() {
    var btn, appendedLi;
    btn = document.getElementById('btn');
    btn.click();
    appendedLi = document.getElementById('list').firstElementChild;
    expect(appendedLi.innerText).toEqual('karma!');
  });

  it("追加されたli要素のテキスサイズは20pxとなるべし", function() {
    var btn, appendedLi;
    btn = document.getElementById('btn');
    btn.click();
    appendedLi = document.getElementById('list').firstElementChild;
    expect(Number(appendedLi.style.fontSize.replace('px', ''))).toEqual(20);
  });

  it("li要素は複数追加できるべし", function() {
    var btn, liCnt;
    btn = document.getElementById('btn');
    btn.click();
    btn.click();
    btn.click();
    liCnt = document.getElementById('list').childElementCount;
    expect(liCnt).toEqual(3);
  });
});
```

さあ、`npm test`でテストを実行してみましょう。

- 実行結果  
![スクリーンショット 2015-05-30 22.18.59.png](https://qiita-image-store.s3.amazonaws.com/0/39330/11d40e9a-b340-c7ab-7433-340c389bd497.png)

おや、なんかFirefoxの方だけエラーが起きてますね。。

あっ、検査文字列を取得するメソッドが`innerText`になっている！
ということでこれを`innerHTML`に変えてみましょう。これでこそマルチブラウザテスト！

- ファイル監視ですでにテストが終わってました  
![スクリーンショット 2015-05-30 22.22.27.png](https://qiita-image-store.s3.amazonaws.com/0/39330/1e375f2d-8955-d712-474c-cf9462be37fd.png)

今度は成功です！
DOMやスタイルに対するHTMLテストが出来ました！

# karmaレポーター

さて、最後にもう少し便利にすることにトライしてみます。

## テストしたHTMLファイルを表示する/テスト結果をもう少しリッチにする

ではテスト結果に彩りを添えるために下記のパッケージをインストールします。

```bash:コンソール
npm i -D karma-spec-reporter karma-jasmine-html-reporter
```

さらに`karma.conf.js`に設定を変更します。結果表示用のreportersをデフォルトのprogressから変更します。

```js:karma.conf.js
module.exports = function (config) {
  config.set({
    …
    reporters: ['spec', 'html'],
    …
  });
};
```

さて、やる事はこれだけです。`npm test`を実行してみましょう。

---

- karma-spec-reporterによって実行結果がリッチに  
![スクリーンショット 2015-05-30 22.37.56.png](https://qiita-image-store.s3.amazonaws.com/0/39330/0c529bf9-0bcb-bbf0-1e0a-7fd72835bdb4.png)

---

- karma-jasmine-html-reporterによってHTMLにテスト後の状態が表示される  
![スクリーンショット 2015-05-30 22.38.43.png](https://qiita-image-store.s3.amazonaws.com/0/39330/1c87ded6-0e0f-0818-17de-23e0175de55d.png)

---

このHTMLはキャプチャではなくて実際にボタンをクリックする事ができます。
**注意点：karma-jasmine-html-reporterをカマすとファイル監視とブラウザ実行の関連付けが切れます。何度もモジュールを書き換えながらテスト結果をチェックしたい場合はオフっておいた方が良さそうです**


# まとめ
## 今回インストールしたパッケージ群まとめ

|パッケージ名| 概要 |
| --- | --- |
| karma | 実ブラウザでJavaScriptコードを実行できる実行環境 |
| karma-cli | -gでインストールすればどこからでもkarmaコマンドを実行できる |
| karma-jasmine | KarmaをJasmineに適合させる事ができる |
| karma-chrome-launcher | Chromeブラウザをを起動できる |
| karma-firefox-launcher | Firefoxブラウザをを起動できる |
| karma-html2js-preprocessor | HTMLファイルをJavaScriptで扱える文字列にコンバートできる |
| karma-spec-reporter | コンソール上のテスト結果表示フォーマットを変更する |
| karma-jasmine-html-reporter | launcherが起動したブラウザにWEB画面を表示する |

## launcher, preprocessor, reporterはここで探す
Karmaを使って他にもいろんな事が出来るので探してみると新しい可能性が見つかるかもしれません。

- [launcher](https://npmjs.org/browse/keyword/karma-launcher)
- [preprocessor](https://npmjs.org/browse/keyword/karma-preprocessor)
- [reporter](https://npmjs.org/browse/keyword/karma-reporter)

今回は以上です。