フロントエンドにテストを導入

**2016-8-8**
※webpack単体の記事を書きました。よろしければこちらもどうぞ [step by stepで始めるwebpack](
http://qiita.com/howdy39/items/48d85c430f90a21075cd)

-------

**2016-5-16**
※karma単体の記事を書きました。よろしければこちらもどうぞ [step by stepで始めるKarma](http://qiita.com/howdy39/items/b9d704e7f84053924da3)

-------

本記事は画面のJavaScriptのテストとかまったくやったことない方
Mocha？webpack？karma？それぞれの解説記事はよく見るけど全体像がよくわからんという方向けです。（数日前の自分です）
全体を通して導入の流れを解説した記事があると全体像が理解しやすいのではと思い書いてみました。


## 前提
Nodejs,npm,chromeが導入済みであること


## 流れ

|Step  |表題  |目的|
|------|---|---|
|Step 0|テスト対象アプリケーションを作成||
|[Step 1](https://github.com/howdy39/study-frontend-testing/tree/step-1)|ライブラリを使わないでテスト|ブラウザのconsole機能でテストを行う|
|[Step 2](https://github.com/howdy39/study-frontend-testing/tree/step-2)|Mochaを導入|テスト結果をリッチにする|
|[Step 3](https://github.com/howdy39/study-frontend-testing/tree/step-3)|Nodejsでテストを実行|ブラウザの世界からNodejsの世界にテストを持って行き、後続のStepへつなげる|
|[Step 4](https://github.com/howdy39/study-frontend-testing/tree/step-4)|webpackを導入|ブラウザ上でモジュールの読み込みができるようにする|
|[Step 5](https://github.com/howdy39/study-frontend-testing/tree/step-5)|assertを導入|テストコードのアサーションを最適化する|
|[Step 6](https://github.com/howdy39/study-frontend-testing/tree/step-6)|karmaを導入 ※[step by stepで始めるKarma](http://qiita.com/howdy39/items/b9d704e7f84053924da3)|ブラウザ上でテストを実行する|

リンク先はソースです。

## ゴール

<img width="890" alt="Screen Shot 2016-05-07 at 8.39.23 AM.png" src="https://qiita-image-store.s3.amazonaws.com/0/35595/d54f4ee8-4fcf-8cd9-68d0-4d5f31a8047d.png">


以後Stepの最後にいれていますが
青背景が実行関連のファイル
緑背景がテスト関連のファイル
赤背景がStepごとの差分です。


## Step 0 テスト対象アプリケーションを作成
テキストボックスが2つあり計算ボタンを押すと加算して表示するアプリケーションを作成します。

```html:index.html
<body>
    <div>
        <input type="number" id="value1"> + <input type="number" id="value2"> = <span id="result"></span><br>
        <input type="button" id="calc" value="計算"></input>
    </div>
    <script src="/src/calc-util.js"></script>
    <script src="/src/index.js"></script>
</body>
```

```javascript:index.js
document.getElementById('calc').addEventListener('click', function () {
    var value1 = Number(document.getElementById('value1').value);
    var value2 = Number(document.getElementById('value2').value);
    
    var result = add(value1, value2);
    document.getElementById('result').innerText = result;
});

```

これがテスト対象のコードになります。

```javascript:calc-util.js
function add(value1, value2) {
    return value1 + value2;
}

```

<img src="https://qiita-image-store.s3.amazonaws.com/0/35595/e88bb5d9-ec04-add1-ef2d-8318e310f8d5.png" alt=" width="300" height="300" />


## Step 1 ライブラリを使わないでテスト
テスト実行用のhtmlを適当に作ってブラウザで表示、デベロッパーツールでログを見るだけの形です。

```html:spec.html
<body>
    <script src="/src/calc-util.js"></script>
    <script src="/test/calc-util-spec.js"></script>
</body>
```

```javascript:calc-util-spec.js
console.info('add関数のテスト');
console.info('1+2は3である');

if (add(1, 2) === 3) {
    console.info('成功')
} else {
    console.error('失敗')
}
```

<img width="357" alt="Screen Shot 2016-05-04 at 9.59.32 AM.png" src="https://qiita-image-store.s3.amazonaws.com/0/35595/65017de1-323e-b8d3-e64d-b0a496528efb.png">



## Step 2 Mochaを導入
前回の状態で一応テストとしては成り立ってはいますが、コンソールエラーを探すとか辛いです。
その問題を解決するために [**Mocha**](https://mochajs.org/)や[**Jasmine**](http://jasmine.github.io/)というテストフレームワークを使います。
今回はMochaを使ってみます。



### テストコードを修正
describeでまとめたりitで検証を定義。
成功だけではつまらないので失敗ケースもついでに追加。
※テスト自体が間違っているだけですが、シンプルにしたいのでm(_ _)m

```javascript:calc-util-spec.js
describe('add関数のテスト', function() {
    it('1+2は3である', function() {
        if (add(1, 2) === 3) {
        } else {
            throw new Error('失敗');
        }
    });
    it('1+2は4である', function() {
        if (add(1, 2) === 4) {
        } else {
            throw new Error('失敗');
        }
    });
});

```

### テスト実行用htmlを修正
css・jsの読み込みやセットアップ処理、出力結果の場所などを追加しています。

```html:spec.html
<head>
    <meta charset="UTF-8">
    <title>Step 2 spec</title>
    <link href="https://cdn.rawgit.com/mochajs/mocha/2.2.5/mocha.css" rel="stylesheet" />
</head>
<body>
    <div id="mocha"></div>
    
    <script src="https://cdn.rawgit.com/mochajs/mocha/2.2.5/mocha.js"></script>
    <script>mocha.setup('bdd')</script>
    
    <script src="/src/calc-util.js"></script>
    <script src="/test/calc-util-spec.js"></script>
    
    <script>mocha.run();</script>
</body>
```

### 実行結果
かっこいい感じの表示になりました。
これならエラーを見つけるのは簡単ですね。
<img src="https://qiita-image-store.s3.amazonaws.com/0/35595/81867d38-9c57-ae5a-77a2-2e71f6c66693.png" alt=" width="300" height="300" />



<img src="https://qiita-image-store.s3.amazonaws.com/0/35595/bdc0d7fb-da85-ffa3-7b80-fd55910c507d.png" alt=" width="300" height="300" />




## Step 3 Nodejsでテストを実行
修正の都度ブラウザで実行するのも辛いです。
またCIを行うためにもブラウザを使わずにNodejsで実行できるようにする必要があります。
Mochaはnpmでインストールすることでコンソール上から実行が可能になります。


### package.jsonを作成
package.json(設定ファイル)を作りたいだけなの返答は適当でOKです

```console
npm init
```

### Mochaをインストール

```console
npm i -D mocha
```

### テストコードを修正
テスト対象コードを読み込むために``require``で指定します。

```javascript:calc-util-spec.js
var calcUtil = require('../src/calc-util.js');

describe('add関数のテスト', function() {
    it('1+2は3である', function() {
        if (calcUtil.add(1, 2) === 3) {
        } else {
            throw new Error('失敗');
        }
    });
    it('1+2は4である', function() {
        if (calcUtil.add(1, 2) === 4) {
        } else {
            throw new Error('失敗');
        }
    });
});

```

### テスト対象コードを修正
requireで読み込むには以下のように``module.export``で括ります。
※実際に動かす画面が動かなくなりますがこれは次のstepで解消します。まずはテストを通します。

```javascript:calc-util.js
module.exports = {
    add : function (value1, value2) {
        return value1 + value2;
    }
}
```


### package.jsonを修正
以下のようにtestをmochaに修正し、``npm test``で実行できるようにします。
※テスト対象のデフォルトが``test/*.js``らしいのでSpecファイルの指定は不要です。

```json:package.json
  "scripts": {
    "test": "mocha",
```

### テスト実行
```console
npm test
```

<img src="https://qiita-image-store.s3.amazonaws.com/0/35595/5cba61e0-7ca4-6627-074c-275823e86b22.png" alt=" width="300" height="250" />

<img src="https://qiita-image-store.s3.amazonaws.com/0/35595/c2bf1a5c-3b81-5ed5-5bb2-dc07600bf8be.png" alt=" width="300" height="250" />



## Step 4 webpackを導入
requireできるようにadd関数をいじってしまったので画面が動かなくなってしまいました。
これを解決するのが [**webpack**](https://webpack.github.io/) や [**browserify**](http://browserify.org/) といったツールを使用します。
今回はwebpackを使用してみます。

### webpackをインストール

```console
npm i -D webpack
```

### configファイルを作成
設定の詳細な意味等はこちらの記事[webpack入門](http://qiita.com/ossan-engineer/items/8352bdeab9ce8c8c00ef)が分かりやすかったので参照してください。
ざっくり書くとindex.jsとrequireされているcalc-util.jsをapp.bundle.jsにまとめる設定になっています。

```console
touch webpack.config.js
```

```javascript:webpack.config.js
module.exports = {
    entry: {
        app: './src/index.js'
    },
    output: {
        path: './dist',
        filename: '[name].bundle.js'
    }
};
```

### package.jsonを修正

以下のようにbuildを追加し、``npm run build``で実行できるようにします。
※ webpackとうつだけでデフォルトでwebpack.config.jsが使用されます。
###

```diff:package.json
  "scripts": {
    "test": "mocha",
+   "build": "webpack",
```


### add関数を呼んでいるjsを修正

テストコードと同じ様にrequireを使って以下のようにします

```javascript:index.js
var calcUtil = require('./calc-util.js');

document.getElementById('calc').addEventListener('click', function () {
    var value1 = Number(document.getElementById('value1').value);
    var value2 = Number(document.getElementById('value2').value);
    
    var result = calcUtil.add(value1, value2);
    document.getElementById('result').innerText = result;
});
```



### webpackを実行

```console
npm run build
```

これでapp.bundle.jsが作成されブラウザで読めるようになりました。



### 読み込むhtmlを修正
もともと2つ読み込んでいたjsが1つになります。

```html:index.html
<body>
    <div>
        <input type="number" id="value1"> + <input type="number" id="value2"> = <span id="result"></span><br>
        <input type="button" id="calc" value="計算"></input>
    </div>
<!--    <script src="/src/calc-util.js"></script> -->
<!--    <script src="/src/index.js"></script> -->
    <script src="/dist/app.bundle.js"></script>
</body>
```

副次的にadd関数がグローバル汚染しなくなるというメリットも生まれました。

<img width="563" alt="Screen Shot 2016-05-07 at 8.38.56 AM.png" src="https://qiita-image-store.s3.amazonaws.com/0/35595/e83b4660-98e3-9939-3b11-5386ffcfe143.png">




## Step 5 assertを導入
テストをもっと楽にしましょう。
ifやthrowが冗長ですよね。それを可能にするのがassertです。
Nodejsに標準に付属しているライブラリのようです。

``require('assert')``を行い``assert()``でテスト対象を指定します。

```javascript:calc-util-spec.js
var assert = require('assert');
var calcUtil = require('../src/calc-util.js');

describe('add関数のテスト', function() {
    it('1+2は3である', function() {
        assert(calcUtil.add(1, 2) === 3);
    });
    it('1+2は4である', function() {
        assert(calcUtil.add(1, 2) === 4);
    });
});
```

だいぶすっきりしました。

<img src="https://qiita-image-store.s3.amazonaws.com/0/35595/3310e104-b523-0b1b-a80c-bba7ef0b3297.png" alt=" width="300" height="250" />


<img width="562" alt="Screen Shot 2016-05-07 at 8.39.06 AM.png" src="https://qiita-image-store.s3.amazonaws.com/0/35595/3576a7a9-be60-a240-439f-b489dd3d8524.png">



## Step 6 karmaを導入
今まではNodejsの世界でテストをしていました。
しかしブラウザによって動かない関数を使っていたらエラーになってしまいます。
それを解決するために[**karma**](https://karma-runner.github.io/0.13/index.html)というブラウザ上でテストを動かすライブラリを使います。


### karma関連のモジュールをインストール

```console
npm i -D karma karma-chrome-launcher karma-mocha karma-webpack
```

karmaが本体です。それ以外がプラグインとなっており
karma-chrome-launcherがchrome起動用。
karma-mochaがmocha用。
karma-webpackがwebpack用です。


**2016-05-08 追記**
karma-cliのグローバルインストールが必要でした。

```console
npm i -g karma-cli
```




### 初期設定ファイルを作成
コマンドを叩き質問にこたえることで簡単に設定ファイル(karma.conf.js)を作ることができます。

```console
karma init

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

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
> 

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.
> test/**/*spec.js
18 04 2016 20:26:02.739:WARN [init]: There is no file matching this pattern.

> 

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

```

Require.jsは必要そうに感じるのですが必要ないです。
※karma-webpackでrequireを変換したファイルを食わせるためです。


### karma.conf.jsを修正
上記のまま後述の``karma start``を実行すると``Uncaught ReferenceError: require is not defined``と出ます。
ブラウザ上で動かしているためrequireが見つからないということですね。
**Step 4**でwebpackを通してrequireを変換した時と同じことをする必要があります。
karmaではpreprocessorsの設定でwebpackを通すように指定することができます。

```diff:karma.conf.js
    preprocessors: {
+       'test/**/**spec.js': ['webpack']
    },
```

### package.jsonを修正
``npm test``で実行できるようにするため修正します。

```diff:package.json
-   "test": "mocha",
+   "test": "karma start",
```

### karmaを起動

```console
npm test
```

<img src="https://qiita-image-store.s3.amazonaws.com/0/35595/0d76b0b6-a6d8-d22e-1823-40ee2347e4bd.png" alt=" width="300" />


Chromeが立ち上がりターミナル上でテスト結果が表示されています。
設定ファイルを作った時の最後の質問でtrueとしているのでテストファイルやテスト対象ファイルを修正した際に、自動でテストが走ります。
テストやソースを修正してからブラウザ画面をF5で更新して確認という手間が省けますね。


<img width="892" alt="Screen Shot 2016-05-07 at 8.39.13 AM.png" src="https://qiita-image-store.s3.amazonaws.com/0/35595/6b5fd434-2650-9fa0-4c9d-709a346014ad.png">



## 余談
図は[draw.io](https://www.draw.io/)で1ファイル内で全て作ってpngエクスポートしてそれぞれ範囲選択キャプチャしてます。
良いやり方をご存知でしたらご教示くださいm(_ _)m