テストカバレージを計測して内容を見てみよう

# はじめに

この記事は[Ponos Advent Calendar 2019](https://qiita.com/advent-calendar/2019/ponos) 20日目の記事です。

ポノスでは月に一度を目標に全社のエンジニアを集めて社内勉強会を開催しています。
こちらはその全社勉強会の資料になります。

せっかく勉強会のために資料を作るのなら公開を前提にして作成してみようということでQiitaで資料を作成してみました。

尚、この資料を利用した社内勉強会は12月16日に開催されました。

# テストカバレージを計測して内容を見てみよう

[ユニットテスト](https://ja.wikipedia.org/wiki/%E5%8D%98%E4%BD%93%E3%83%86%E3%82%B9%E3%83%88)についての話はよく耳にされているかと思います。

テストがないプロジェクトにテストを追加したり
新しいプロジェクトで[テスト駆動開発](https://ja.wikipedia.org/wiki/%E3%83%86%E3%82%B9%E3%83%88%E9%A7%86%E5%8B%95%E9%96%8B%E7%99%BA)を取り入れてみたりすることは非常に意義があることだと思います。

以下は一例ですがユニットテストを取り入れる際になぜそれを実施したいのかは明白だと思います。
・バグを減らしたい
・関数の入力値と出力値を保証したい
・コード修正が他に波及していないかどうかを判定する仕組みを作りたい
・CI/CDパイプラインを構築するための基礎となるため
　等々

プロジェクト全体（ソースコード全体）を俯瞰した場合、
上記の目的で導入したテストは<font color="red">**何をどの程度**</font>実施しているのでしょうか？

これを確認するための一つの指標として<font color="red">**テストカバレージ**</font>というものがあります。

以下にテストの例を載せます。
（以下は[node.js](https://nodejs.org/)のアプリケーションのユニットテストの例です。
ユニットテストとテストカバレージの計測には[jest](https://jestjs.io/)を利用しています。）

```
> jest

 PASS  test/model/command_results.test.js
 PASS  test/runner/http.test.js
 PASS  test/runner/snmp.test.js
 PASS  test/model/command_result.test.js
 PASS  test/runner/ping.test.js
 PASS  test/helper/command_runner.test.js (6.156s)

Test Suites: 6 passed, 6 total
Tests:       20 passed, 20 total
Snapshots:   0 total
Time:        8.692s, estimated 16s
Ran all test suites.
```

上記のコンソール出力結果は**詳細出力とテストカバレージの計測を<font color="red">行っていません</font>**。
この結果から読み取れるのは
・設定したユニットテストが全てパスしていること
・ユニットテストの実施にかかった時間
・ユニットテストの個数
・どのファイルがユニットテストされたのか？（ただし網羅されているかどうかは分からない）
です。

これでは
・どんな項目が具体的にテストされたのか？
・どの程度、テストされたのか？
が分かりません。

このままだとシステム全体の規模が大きくなればなるほど適切にユニットテストが追加されているのか不安になります。

---

一方で下記のコンソール出力結果は**詳細出力とテストカバレージの計測を<font color="red">行っています</font>**。
こちらからは以下が**<font color="red">追加で</font>**読み取れます。
・具体的に何がテストされたのか（verboseオプション）
・コードのどの部分は少なくともテストケースがなめているのか？（coverageオプション）
　・全ての条件分岐（if文等）がユニットテストで網羅されているのか？等
　・Jestの各種パラメータの説明は以下になっています。
　　・Stmts: 命令網羅
　　・Branch: 分岐網羅
　　・Funcs: 関数が網羅されているかどうか
　　・Line: ソースコードの各行が実行されたかどうか
　　（https://stackoverflow.com/questions/26618243/how-do-i-read-an-istanbul-coverage-report
　　　https://www.guru99.com/code-coverage.html）

この情報はプロジェクトに関わるエンジニアの数が増えるほど、プロジェクトの規模（コードの量）が増えれば増えるほど効果を発揮するのではないかと思います。

```
> jest "--verbose" "--coverage"

 PASS  test/model/command_results.test.js (10.608s)
  ✓ can init. (6ms)
  ✓ can update. (1ms)
  ✓ can not update without init. (1ms)
  ✓ can be json formatted value. (1ms)
  ✓ can be csv formatted value.

 PASS  test/runner/http.test.js (11.248s)
  ✓ can not be reached to a nonexistance url of a http protocol (97ms)
  ✓ can not be reached to a nonexistance url of a https protocol (33ms)
  ✓ can be reached to an existance url of a http protocol (456ms)
  ✓ can be reached to an existance url of a https protocol (189ms)

 PASS  test/model/command_result.test.js
  ✓ function isOK works fine (1ms)
  ✓ function message works fine (1ms)
  ✓ this model can be string

 PASS  test/runner/snmp.test.js
  ✓ can be reached to an open snmp server (133ms)
  ✓ can not be reached to an unknown host (15ms)

 PASS  test/helper/command_runner.test.js (15.038s)
  ✓ can be reached to an open snmp server by using CommandRunner (1163ms)
  ✓ Result of dry-run should be true (1002ms)
  ✓ Result of an undefined command should be false (1006ms)
  ✓ Result of an exception in an existing command should be false (1078ms)

 PASS  test/runner/ping.test.js
  ✓ can be reached to a localhost (24ms)
  ✓ can not be reached to an unexistence ip address (2029ms)

---------------------|----------|----------|----------|----------|-------------------|
File                 |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
---------------------|----------|----------|----------|----------|-------------------|
All files            |    87.13 |    72.22 |    81.58 |    88.78 |                   |
 helper              |      100 |      100 |    91.67 |      100 |                   |
  command_runner.js  |      100 |      100 |      100 |      100 |                   |
  logger.js          |      100 |      100 |    83.33 |      100 |                   |
 model               |    65.71 |    33.33 |    64.71 |     69.7 |                   |
  command_result.js  |      100 |      100 |      100 |      100 |                   |
  command_results.js |    58.62 |    33.33 |    53.85 |    62.96 |... 41,42,43,45,46 |
 runner              |    97.37 |     87.5 |      100 |    97.37 |                   |
  http.js            |    94.12 |       75 |      100 |    94.12 |                24 |
  ping.js            |      100 |      100 |      100 |      100 |                   |
  snmp.js            |      100 |      100 |      100 |      100 |                   |
---------------------|----------|----------|----------|----------|-------------------|
Test Suites: 6 passed, 6 total
Tests:       20 passed, 20 total
Snapshots:   0 total
Time:        31.021s
Ran all test suites.
```

ユニットテストそのものは人が記載するものなのでバグが入る余地があります。
そのため、カバレージの計測結果そのものを過信するのは禁物ですがプロジェクト全体からコードの全体像を俯瞰する一つの指標としては役に立ちます。

コードレビュー等を通してユニットテストの品質を維持することができればテストカバレージの計測は次のテストを設計するための良い指標/指針になるでしょう。

より具体的にテストカバレージについて知りたい方は
・[テストカバレッジ100%を追求しても品質は高くならない理由と推奨されるカバレッジの目標値について](https://qiita.com/bremen/items/d02eb38e790b93f44728)
・[ホワイトボックステストにおけるカバレッジ（C0/C1/C2/MCC）について](https://qiita.com/bremen/items/8b6542467d2a0066e5af)
等を読むと良いでしょう。