javascriptまたはEcmaScript6(ES6)のプログラムの品質を保つ方法を、備忘録としてまとめてみたいと思います。

今回は第3回目です。istanbulというnpmパッケージを使って、プログラムが隈なくテストされているのかを計測したいと思います。これをテストカバレッジ(テストの網羅率)の取得と呼んでいます。

前回は、javascriptやES6における、テストの書き方や使い方を簡単に解説していきました。期待した通りにプログラムが動作するか、テストコードで検証する事によって、プログラムの動作を隈なく検証することができます。

私自身もテスト初心者ですので、できるだけ平たく説明していきます。もしこれは違う!という内容があればツッコミをお待ちしております。

目次

javascriptで用いるモジュールをおさらい

第一回目でも触れましたが、テストやテストカバレッジ取得を行うために、以下のnpmパッケージを使います。

  • mocha (モカ)

    テストフレームワークと呼ばれているパッケージです。テストのプログラムを実行させることができる枠組みです。

  • power-assert (パワーアサート)

    テストフレームワークのmochaに、想定した動作ルールを書くことができ、想定外のプログラム動作が見つかるとエラーで教えてくれます。この動作ルールのことをアサーションと呼んでいます。

  • intelli-espower-loader (なんて読むんだろ?)

    power-assertを実行するために、コードを変換してくれるパッケージ。power-assertのテストを実行する際に、mochaの内部で利用します。

  • istanbul (イスタンブール)

    テストのカバー率を調べることができます。プログラムにテストを行えていない箇所があれば、その箇所をエラーで教えてくれるため、テストを網羅するのに役立ちます。この網羅率の事をカバレッジと呼んでいます。今回はistanbulの中でmochaのテストを実行し、同時にカバレッジも取得していく流れとなります。

今回は、上記のパッケージを使って、記述したjavascriptのプログラムのテストを行いつつ、テストのカバー率である「カバレッジ」を取得する方法について後述していきたいと思います。

パッケージのインストール

それでは、必要なパッケージを用意していきたいと思います。これから書いていくコードは、前回のものを利用し、一部は変更又は書き足していく形で進めていきたいと思います。もし前回をご覧になっていない方は、まずそちらをご覧いただくか、以下のサンプルコードをご確認ください。

サンプルコード

サンプルコードはgithubにアップしていますので、そちらをご覧ください。

まずは、サンプルコードを実行するプロジェクトjs-test-practiceディレクトリを作成し、npmパッケージをインストールします。

bash
1
2
3
4
5
$ git clone https://github.com/tea3/js-test-practice.git
$ mkdir js-test-practice
$ cd js-test-practice
$ npm init
$ npm install --save-dev istanbul mocha power-assert intelli-espower-loader
save-devオプションって何?

npm installコマンドの--save-devオプションは、開発で使うパッケージという意味合いを示すために指定しています。本体のプログラムでは使用せず、テストなど本体のプログラムで使わないパッケージがあれば、このオプションを指定してインストールしましょう。

また、node.jsやnpm、そしてコマンドラインについてご不明な場合は以下をご覧ください。

本体のプログラムを作成

続いて、プログラムの本体を書いて見たいと思います。

本体のプログラムは前回記事の「外部ファイルの読み込みもテストで検証できる」の項目と同様の内容です。./index.jsと外部モジュール./lib/sample-lib.jsを作成して、挨拶のプログラムを用意したいと思います。

./index.js
1
2
3
4
5
6
// node.js v8以降で動作します。
const lib = require('./lib/sample-lib.js') // sample-lib.jsを読み込む

exports.hi = (name) => "やあ!"+ name
exports.hello = (name) => lib.hello(name) // sample-lib.jsの関数を利用している
exports.helloSum = (name , ...arg) => `${lib.hello(name)}。合計は${lib.sum(...arg)}です。` // sample-lib.jsの関数を利用している

./lib/sample-lib.jsは下記のようなコードになります。上記コード./index.jsで読み込んで利用します。

./lib/sample-lib.js
1
2
3
4
5
6
7
8
9
10
// 挨拶を返す関数
exports.hello = (name) => "はろー!"+ name
// 合計を返す関数
exports.sum = (...arg) => {
let result = 0
for(let i of arg){
result += i
}
return result
}

テストのプログラムを用意する

それでは、先程用意した本体プログラムのメソッドhi(name)hello(name)helloSum(name, ...arg)の結果を検証するテストコード./test/test.jsを書いていきたいと思います。

こちらも前回記事と同じ内容です。

./test/test.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// node.js v8以降で動作します。
const assert = require('power-assert') // power-assertをインポート
const myModule = require('../index') // プログラム本体である、index.jsを読み込む

// テストコード
describe('作ったプログラムを次の項目ごとにテストします' , () => {
truedescribe('1. 挨拶のテスト その1', () => {
it('「こんにちは!」と挨拶しないとだめ', () => {
assert.equal(myModule.hi('太郎'), 'こんにちは!太郎')
})
})

// 新たに追加するテストコード
describe('2. 挨拶のテスト その2', () => {
it('「はろー!」と挨拶しないとだめ', () => {
assert.equal(myModule.hello('太郎'), 'はろー!太郎')
})
})

// 新たに追加するテストコード
describe('3. 挨拶と合計を計算するテスト', () => {
it('挨拶と合計を計算しないとだめ', () => {
assert.equal(myModule.helloSum('太郎',1,2,3), 'はろー!太郎。合計は6です。')
})
})

})

次にpackage.jsonを開き、scriptsの項目を以下のように変更します。

./package.json
1
2
3
4
5
6
7
8
9
{
"name": "test-practice",
"version": "1.0.0",
"description": "javascript test practice",
"main": "index.js",
"scripts": {
"test": "mocha test/test.js --require intelli-espower-loader"
},
...

それでは、上記で書いたテストを実行してみましょう。

bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ npm run test

> mocha test/test.js



作ったプログラムを次の項目ごとにテストします
1. 挨拶のテスト その1
✓ 「やあ!」と挨拶しないとだめ
2. 挨拶のテスト その2
✓ 「はろー!」と挨拶しないとだめ
3. 挨拶と合計を計算するテスト
✓ 挨拶と合計を計算しないとだめ


3 passing (1ms)

3 passingと表示されれば、本体のプログラム./index.jsと外部モジュール./lib/sample-lib.jsに含まれる3つのメソッドが問題なく動作することが検証された事になります。

ここまでは前回と同様の結果です。

カバレッジを取得する

ここからが本題です。先程行ったテストと併せて、テストの網羅率(=カバレッジ)を取得していきたいと思います。istanbulというパッケージを使いますので、インストールされていない場合には入れておきましょう。

bash
1
$ npm install --save-dev istanbul

次にpackage.jsonを開き、scriptsの項目に、新しいコマンドtest-covを追記します。

./package.json
1
2
3
4
5
6
7
8
9
10
{
"name": "test-practice",
"version": "1.0.0",
"description": "javascript test practice",
"main": "index.js",
"scripts": {
"test": "mocha test/test.js --require intelli-espower-loader",
"test-cov": "istanbul cover --print both _mocha -- test/test.js --require intelli-espower-loader"
},
...

上記のように、mochaのテストをistanbulから実行するようにtest-covというコマンドを用意します。それでは、test-covを実行してみましょう。

bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
$ npm run test-cov

> istanbul cover --print both _mocha -- test/test.js --require intelli-espower-loader



作ったプログラムを次の項目ごとにテストします
1. 挨拶のテスト その1
✓ 「やあ!」と挨拶しないとだめ
2. 挨拶のテスト その2
✓ 「はろー!」と挨拶しないとだめ
3. 挨拶と合計を計算するテスト
✓ 挨拶と合計を計算しないとだめ


3 passing (1ms)

=============================================================================
Writing coverage object [/Users/**/js-test-practice/coverage/coverage.json]
Writing coverage reports at [/Users/**/js-test-practice/coverage]
=============================================================================
-----------------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
-----------------------|----------|----------|----------|----------|----------------|
js-test-practice/ | 100 | 100 | 100 | 100 | |
index.js | 100 | 100 | 100 | 100 | |
js-test-practice/lib/ | 100 | 100 | 100 | 100 | |
sample-lib.js | 100 | 100 | 100 | 100 | |
-----------------------|----------|----------|----------|----------|----------------|
All files | 100 | 100 | 100 | 100 | |
-----------------------|----------|----------|----------|----------|----------------|


=============================== Coverage summary ===============================
Statements : 100% ( 14/14 )
Branches : 100% ( 0/0 )
Functions : 100% ( 0/0 )
Lines : 100% ( 10/10 )
================================================================================

上記のように、テストの結果に続いて、カバレッジ取得の結果が出力されます。

全ての結果が100と表示されれば、本体プログラムの./index.js./lib/sample-lib.jsが漏れなくテスト出来たことになります。

また、カバレッジの結果は./coverageというディレクトリが作成され、./coverage/Icov-report/index.htmlをブラウザで開くと、ブラウザからもカバレッジを確認する事ができます。

カバレッジレポートをブラウザで確認することができる
カバレッジレポートをブラウザで確認することができる ©

上記画像の中で、緑色で示されているコードは、テストによってカバーされている箇所を示しています。今回はテストのコードによって全てが網羅されていることが分かります。

テストが網羅できていないと、どうなるの?

先程は、カバレッジが100%の例を挙げました。それでは、テストの網羅が不完全だとどうなるのでしょうか?前回行ったテストの一部をコメントアウトして、再びカバレッジを取得してみましょう。

テストのコード./test/test.jsを下記のように、一部コメントアウトし、2つ目と3つ目のテストを無効にしてみましょう。

./test/test.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// node.js v8以降で動作します。
const assert = require('power-assert') // power-assertをインポート
const myModule = require('../index') // プログラム本体である、index.jsを読み込む

// テストコード
describe('作ったプログラムを次の項目ごとにテストします' , () => {
describe('1. 挨拶のテスト その1', () => {
it('「こんにちは!」と挨拶しないとだめ', () => {
assert.equal(myModule.hi('太郎'), 'こんにちは!太郎')
})
})

// // 新たに追加するテストコード
// describe('2. 挨拶のテスト その2', () => {
// it('「はろー!」と挨拶しないとだめ', () => {
// assert.equal(myModule.hello('太郎'), 'はろー!太郎')
// })
// })

// // 新たに追加するテストコード
// describe('3. 挨拶と合計を計算するテスト', () => {
// it('挨拶と合計を計算しないとだめ', () => {
// assert.equal(myModule.helloSum('太郎',1,2,3), 'はろー!太郎。合計は6です。')
// })
// })

})

それでは再びtest-covを実行してみましょう。

bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
$ npm run test-cov

> istanbul cover --print both _mocha -- test/test.js --require intelli-espower-loader



作ったプログラムを次の項目ごとにテストします
1. 挨拶のテスト その1
✓ 「やあ!」と挨拶しないとだめ


1 passing (1ms)

=============================================================================
Writing coverage object [/Users/**/js-test-practice/coverage/coverage.json]
Writing coverage reports at [/Users/**/js-test-practice/coverage]
=============================================================================
-----------------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
-----------------------|----------|----------|----------|----------|----------------|
js-test-practice/ | 71.43 | 100 | 100 | 100 | |
index.js | 71.43 | 100 | 100 | 100 | |
js-test-practice/lib/ | 28.57 | 100 | 100 | 33.33 | |
sample-lib.js | 28.57 | 100 | 100 | 33.33 | 3,4,5,7 |
-----------------------|----------|----------|----------|----------|----------------|
All files | 50 | 100 | 100 | 60 | |
-----------------------|----------|----------|----------|----------|----------------|


=============================== Coverage summary ===============================
Statements : 50% ( 7/14 )
Branches : 100% ( 0/0 )
Functions : 100% ( 0/0 )
Lines : 60% ( 6/10 )
================================================================================

上記の結果は、テストは1つ実行され、問題なく通っています。ただし、テストカバレッジはStatements 50%となっており、まだテストされていない箇所があることを示しています。

生成されたカバレッジレポート(./coverage/Icov-report/index.html)をブラウザで開いてみましょう。

カバレッジレポートをブラウザで確認することができる

上記画像を見ると、赤く示された箇所のプログラムがテストされていない事がわかります。赤い箇所は、先程コメントアウトしたテストコードと一致しています。

実際のプログラムでは、メインプログラムの極力広い範囲がカバーされるように、テストのコードを書いていく事になります。

まとめ

という事で、今回はjavascriptやES6における、テストの網羅率(=カバレッジ)を取得する方法を分かりやすくまとめました。

npmパッケージのistanbulを使うと、プログラムが期待通りに動作するのか否かや、また同時に、テストコードがメインプログラムの動作を隈なく検証することができたのかを機械的に計測することができます。

次回は、ESLintを使って、JavascriptやES6のコーディングをチェックする方法について解説していきます。