Cypress で Code Coverage を測定する。

#あらすじ
Cypress で Code Coverage 測定してみました。


#インデックス
- [Cypress Code Coverage やってみた](#cypress-code-coverage-やってみた)


#Cypress Code Coverage やってみた
#####参考
- https://github.com/cypress-io/code-coverage
- https://docs.cypress.io/guides/tooling/code-coverage.html
- https://vuejsdevelopers.com/2020/07/20/code-coverage-vue-cypress/

###環境設定
Vue + Vuetify + TypeScript のプロジェクトを前提にしてます。

#####Vue プロジェクトの作成
Babel, TypeScript, Lint を追加しておきます。

```
vue create {YOUR_PROJECT_NAME}
```

#####Vuetify の追加
```
vue add vuetify
```

tsconfig.json の types に vuetify と cypress を入れるのを忘れずに。

#####Cypress のインストール
```
yarn add --dev cypress
```

#####Cypress Test Runner を launch
```
yarn run cypress open
```

#####TypeScript のサポートをインストール
```
yarn add --dev @bahmutov/add-typescript-to-cypress
```

#####@cypress/code-coverage plugin をインストール
```
yarn add --dev @cypress/code-coverage
```

#####babel-plugin-intanbul をインストール
@cypress/code-coverage 自体にはカウンターを設置する機能はないので、 babel を頼ります。

```
yarn add --dev babel-plugin-istanbul
```

#####babel core をインストール

```
yarn add --dev @babel/core
```

#####babel.config.js を編集
.js, .ts, .vue のファイルに対し、 instrument してくれるように設定します。

```javascript:babel.config.js
module.exports = {
  presets: ["@vue/cli-plugin-babel/preset"],
  plugins: [
    [
      "babel-plugin-istanbul",
      {
        extension: [".js", ".ts", ".vue"]
      }
    ]
  ]
};
```

#####cypress/support/index.ts を編集
code-coverage のプラグインをインポートします。

```javascript:cypress/support/index.ts
// Import commands.js using ES2015 syntax:
import './commands'

// Alternatively you can use CommonJS syntax:
// require('./commands')

import "@cypress/code-coverage/support";
```

#####cypress/plugins/index.js を編集
インポートした code-coverage のプラグインを config に反映します。

```javascript:cypress/plugins/index.js
// promisified fs module
const fs = require("fs-extra");
const path = require("path");

// コンフィグファイルを読み込む。
const getConfigurationByFile = (on, config) => {
  // accept a configFile value or use development by default

  const file = config.env.configFile || "dev";
  const pathToConfigFile = path.resolve(
    ".",
    "cypress/config",
    `${file}.json`
  );
  return fs.readJson(pathToConfigFile);
};

const cypressTypeScriptPreprocessor = require("./cy-ts-preprocessor");

/**
 * @type {Cypress.PluginConfig}
 */
module.exports = async (on, config) => {
  require("@cypress/code-coverage/task")(on, config);
  on("file:preprocessor", cypressTypeScriptPreprocessor);

  config = await getConfigurationByFile(on, config);

  return Object.assign({}, config, {
    fixturesFolder: "cypress/fixtures",
    integrationFolder: "cypress/e2e",
    screenshotsFolder: "cypress/screenshots",
    videosFolder: "cypress/videos",
    supportFile: "cypress/support/index.ts",
  });
};
```

#####cypress.json を編集
codeCoverageTasksRegistered を true にすることでプラグインが有効になります。

```json:cypress.json
{
    "baseUrl": "http://localhost:8080/",
    "env": {
        "codeCoverageTasksRegistered": true
    }
}
```

#####package.json を編集
Cypress 関連のコマンドを追加します。
Vue CLI にて Cypress をインストールした際は、サーバの立ち上げから Cypress の起動を一気に行ってくれるコマンドがすでに用意されています。
今回は Vue CLI を使っていないので、 start-server-and-test を利用します。
https://docs.cypress.io/guides/guides/continuous-integration.html#Boot-your-server

```
yarn add --dev start-server-and-test 
```

```json:package.json
"scripts": {
    "cy:open": "cypress open --env configFile=qa",
    "cy:run": "cypress run",
    "test:e2e": "start-server-and-test serve http://localhost:8080 cy:open"
  },
```

※ 一部抜粋。

###カバレッジを測定する
今回のテスト対象のコードはこちら。

```vue:HelloWorld.vue
<template>
  <v-app>
    <v-container>
      <div class="Home">
        <h1 data-cy="greeting">{{ greeting }}</h1>
        <v-row>
          <v-col cols="12" sm="6">
            <v-btn
              block
              color="error"
              @click="greeteEnglish"
              data-cy="greeteEnglishButton"
              >英語で挨拶</v-btn
            >
          </v-col>
          <v-col cols="12" sm="6">
            <v-btn
              block
              color="success"
              @click="greeteJapanese"
              data-cy="greeteJapaneseButton"
              >日本語で挨拶</v-btn
            >
          </v-col>
        </v-row>
      </div>
    </v-container>
  </v-app>
</template>

<script lang="ts">
/*eslint @typescript-eslint/no-explicit-any: "off"*/
import { Component, Emit, Vue } from "vue-property-decorator";

@Component
export default class TCHelloWorldVue extends Vue {
  name = "HelloWorld"

  greeting = "挨拶"

  @Emit('greeteEnglish')
  greeteEnglish() {
    this.greeting = "Hello.";
  }
  
  @Emit('greeteJapanese')
  greeteJapanese() {
    this.greeting = "こんにちは。";
  }
};
</script>

```

テストはこんな感じ。
「英語で挨拶」ボタンを押して、「 Hello. 」と表示されるかテストします。

```typescript:HelloWorld.spec.ts
describe("My First Test", () => {
  it("Visits the app root url", () => {
    cy.visit("http://localhost:8080");
  });
  it("Click English greeting Button", () => {
    cy.get("[data-cy=greeteEnglishButton]").click();
    cy.get("[data-cy=greeting]").contains("Hello.");
  });
});
```

Cypress を起動しましょう。

```
yarn test:e2e
```

GUI の結果に注目してみます。
`BEFORE ALL`, `AFTER EACH`, `AFTER ALL` にて `@cypress/code-coverage` の処理が行われています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/557096/8e7e0166-3fc4-8a07-329e-1338dc2a7311.png)

最後にレポートを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/557096/a7a55c6b-5514-2aaa-f99c-7c702b75d0f1.png)

今回のテストでは「日本語で挨拶」する関数を読んでいないので、レッドになっています。
日本語のテストも追加しましょう。

```typescript:Home.spec.ts
describe("My First Test", () => {
  it("Visits the app root url", () => {
    cy.visit("http://localhost:8080");
  });
  it("Click English greeting Button", () => {
    cy.get("[data-cy=greeteEnglishButton]").click();
    cy.get("[data-cy=greeting]").contains("Hello.");
  });
  it("Click Japanese greeting Button", () => {
    cy.get("[data-cy=greeteJapaneseButton]").click();
    cy.get("[data-cy=greeting]").contains("こんにちは。");
  });
});
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/557096/0e612333-8046-1096-c179-f96e75652936.png)

すべて ⛳️ になりました。めでたしめでたし。


#E2E テストで確認出来ないコードについて
例えば E2E テストでは case 文の default を確認出来ない場合があります。
この場合は Cypress にて unit test を書くことになります。


#まとめ
E2E テストでも Code Coverage を取ることで品質上げていきたい。