🧪

nyc (istanbul) でE2Eテストのカバレッジを取得する

2023/10/22に公開

E2Eテストや結合テストと呼ばれるような、GUIを用いて実システムの挙動を確認するテストはバックエンドのコードカバレッジが取れない。……と思い込んでいたのだが、ちょっと前に同僚から「E2Eテスト実行中に通ったコードと通ってないコードを測れば良いのでは」と言われ、目から鱗が落ちる思いだった。考えてみれば当たり前なのだが。

NodeJSでカバレッジを取るには nyc というコマンドラインツールを使うのが一般的なようだった。このツールは istanbul というカバレッジ計測ツールを使うためのコマンドラインツールだ。

公式のものも含め、ググるとサンプルがたくさん出てくるのだが、どうもうまく行かず、解決に3日ほど費やした。最終的に nyc の気持ちが何となく分かってきたので、備忘録も兼ねてまとめておく。

つまづいたポイント

基本的な使い方としては、 nyc をインストールして、テスト実行時に nyc を付けて実行すれば良い。以下は 公式 から取得したもの。

// package.json

{
  "scripts": {
    "test": "mocha",
    "coverage": "nyc npm run test"
  }
}

さて、今回はE2Eテストなので、Webアプリケーションの起動とテストコードの実行をそれぞれ叩く必要がある。例えば、以下のようなイメージである。

// package.json

{
  "scripts" : {
    "run": "node src/server.js",
    "test": "mocha"
  }
}

カバレッジを取りたいのは src 内にあるアプリケーションのコード群なので、 nycrun 側に付けることになる。

// package.json

{
  "scripts" : {
    "run": "nyc node src/server.js",
    "test": "mocha"
  }
}

ところが、これを実行したところ、カバレッジが全く取られなかった。

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |       0 |        0 |       0 |       0 |                   
----------|---------|----------|---------|---------|-------------------

ググると、 all オプションを付ける必要があるとのことだったで、 package.json に付け足してみた。

// package.json

  "nyc": {
    "all": true,
    "include": [
      "src/**/*.js"
    ],
    "reporter": [
      "text",
      "html"
    ]
  },

すると、カバレッジには変化があったのだが、ほとんどの項目が0%になってしまう。少なくとも src/index.jsは絶対に通るはずなので、これはおかしい。

------------------|---------|----------|---------|---------|-------------------
File              | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
------------------|---------|----------|---------|---------|-------------------
All files         |       0 |        0 |       0 |       0 |                   
 src              |       0 |        0 |       0 |       0 |                   
  index.js        |       0 |        0 |       0 |       0 | 16-61             
 src/lib          |       0 |        0 |       0 |       0 |                   
  authConfig.js   |       0 |        0 |       0 |       0 | 10-68             
  hashPassword.js |       0 |      100 |       0 |       0 | 4-7               
 src/routes       |       0 |        0 |       0 |       0 |                   
  items.js        |       0 |        0 |       0 |       0 | 2-219             
  login.js        |       0 |        0 |       0 |       0 | 2-28              
  order.js        |       0 |        0 |       0 |       0 | 3-332             
  public.js       |       0 |      100 |       0 |       0 | 5-10              
  signUp.js       |       0 |        0 |       0 |       0 | 4-30              
  users.js        |       0 |        0 |       0 |       0 | 2-18              
------------------|---------|----------|---------|---------|-------------------

やったこと

StackOverflowの記事 によると、 nyc instrument というコマンドを使う必要があるらしい。このコマンドは、元のソースコードにカバレッジ計測用のコードを付け足したものを出力してくれる。

例えば、以下のように実行すると、 src 内のコードをトランスパイルして .nyc_instrumented/server に出力してくれる。

$ npx nyc instrument src ./.nyc_instrumented/server

アプリケーションを起動する際には、元の src/index.js ではなく、 ./.nyc_instrumented/server/index.js を実行する。この際も node ではなく nyc node のようにする必要がある。

// package.json
	
{
  "scripts" : {
    "run": "nyc node src/server.js",
    "run:instrument": "nyc instrument src ./.nyc_instrumented/server && nyc node ./.nyc_instrumented/server.js",
    "test": "mocha",
    "report": "nyc report"
  }
}

テスト実行は以下のような順序になる。

$ npm run run:instrument
$ npm run test
$ npm run report

すると、以下のようにカバレッジが出力される。

-------------------------------------------------------------
File              | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s                                                  
------------------|---------|----------|---------|---------|--------------------------------------------------------------------
All files         |   35.58 |     12.9 |    36.2 |   35.74 |                                                                    
 src              |      92 |       50 |     100 |      92 |                                                                    
  index.js        |      92 |       50 |     100 |      92 | 52-53                                                              
 src/lib          |   93.54 |       50 |     100 |   93.33 |                                                                    
  authConfig.js   |   92.59 |       50 |     100 |    92.3 | 41,49                                                              
  hashPassword.js |     100 |      100 |     100 |     100 |                                                                    
 src/routes       |   20.37 |     5.76 |   21.27 |   20.67 |                                                                    
  items.js        |   23.43 |     12.5 |   15.38 |   23.43 | 55-66,76-83,96-110,124-139,154-177,188-200,207-219                 
  login.js        |      70 |       50 |      75 |      70 | 6,27-28                                                            
  order.js        |   12.38 |        0 |    4.54 |   12.74 | ...176-178,183-188,197-266,274-290,298-302,309-316,320-324,328-332 
  public.js       |     100 |      100 |     100 |     100 |                                                                    
  signUp.js       |   18.75 |        0 |   33.33 |   18.75 | 7-10,14-30                                                         
  users.js        |   15.38 |        0 |   33.33 |   15.38 | 3-6,10-18                                                          
------------------|---------|----------|---------|---------|--------------------------------------------------------------------

おわりに

nyc は複数のテストのカバレッジをマージできるらしいので、E2Eテストとユニットテストの結果をマージして全体でどの程度のカバレッジが出ているか算出できる。このカバレッジが100%になれば完璧というわけではなく、例えばフロントエンドとバックエンドとでデータフォーマットが違うみたいなケースには対応出来ないのだが、定量的なカバレッジのメトリクスとしては非常に役に立つと思う。他の言語でも似たような機能があると思うので(Railsとかにもあると聞いた)、時間があるときに試してみたい。

Discussion