精神安定剤としての「E2E自動テスト」と「CI（継続的インテグレーション）」のすすめ

アドベントカレンダー私が担当する最後の記事は、アプリの品質を保つ上で最も重要な`テスト`についてです。
以下の記事の続きという前提なので、`React`+`AWS`で作成したWebアプリ（SPA）に自動テストのフローを追加するという流れで今回の解説を行います。

>[AWS Amplify / AppSyncで画像投稿webアプリのサーバサイドを実装する](https://qiita.com/stranger1989/items/ebfce4617c748d131b10)

`テスト`と聞くと「あんまやる気でねー」となるかもしれませんが、テストがないとリリース時に常に肝を冷やしながら作業をするはめになるので、そんなリスクを犯すくらいならテストしっかり書いておくことで、**精神安定剤**のかわりになってくれます。
そんなわけで、弊社では自動テストコードをしっかり書いて、作業時・リリース時に必ず回すというフローに対してかなり重きをおいています。1回テストコードを書いておけば、繰り返し使いまわせますしね。

テストと一言で言っても、`単体テスト`・`結合テスト`・`機能テスト`・`受け入れテスト`など様々な種類がありますが、本記事で取り上げるのは本番環境に近い形でアプリケーションを起動し、UIを操作しながら正しい挙動をするかどうかを確認するE2E（インテグレーション）テストについてです。

弊社では、`circleci`と`testcafe`を組み合わせて`e2eのテスト自動化`+`CI化`を図っているので、そこで得た知見とつらみ、類似サービスで気になっていた`github actions`・`cypress`も使いながらベストプラクティスを模索していきます。

# <font color="LightSeaGreen">TestCafeの特徴</font>
> [testcafe](https://devexpress.github.io/testcafe/)
> A node.js tool to automate end-to-end web testing
> Write tests in JS or TypeScript, run them and view results

### メリット

- マルチブラウザ（windows IE・Edge、Mac Safariなど）でテストを動作させることができる
- Remoteでテストを動かすことができるので、モバイル端末などでも簡単に動作させることができる

### デメリット

- テスト動作が若干もっさりしている
- テスト中の画面を動画で残しておくことができない
- ブラウザのGUIが使いづらい
デバッグがしにくい

# <font color="LightSeaGreen">TestCafeを動かしてみる</font>

testcafeモジュールをdev環境にインストールします。

```shell
$ npm i -D testcafe
```

テストファイルを記述します。
今回はどのような動作をするかお見せするだけなので単純に、ローカル環境に立ち上げたアプリに対して、ログイン・ログアウトが正常通り行われるかのテストを行います。
testcafeではテストコードファイルはどこに配置しても、testcafe cli でパスを指定すると動作させることができます。

```typescript:testcafe/test1.ts（一例）
import { Selector, Role } from 'testcafe';

const URL = 'http://localhost:3000/';

fixture`ユーザー認証系のテスト`.page`${URL}`;

// ログイン状態を作る共通関数
export const regularAccUser = Role(
  URL,
  async (t: TestController) => {
    await t
      .typeText('input[data-test="username-input"]', 'test') // Domを指定して入力
      .typeText('input[data-test="sign-in-password-input"]', 'test1234');

    await t
      .click('button[data-test="sign-in-sign-in-button"]') // Domを指定してクリック
      .wait(3000);
  },
  { preserveUrl: true }
);

// テストケースを記述
test('ログインのテスト', async t => {
  await t
      .setNativeDialogHandler(() => true)
      .useRole(regularAccUser);
});

test('ログアウトのテスト', async t => {
  await t
      .setNativeDialogHandler(() => true)
      .useRole(regularAccUser);

  await t
      .click('button[data-test="sign-out-button"]');
});

```

```shell:chromeでテストする場合
$ npx testcafe chrome testcafe/test1.ts
```
![a99584eb492ff7141f05b39a2a9e8ae5.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/1abe67b3-f736-bbf4-bba7-bb3ba9cd983b.gif)
![3ddfed61192c4f2d147771d7932528c7.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/05cb6521-23c0-5bfc-e9b6-b51bdb1de681.gif)

```
Running tests in:
 - Chrome 79.0.3945.88 / macOS 10.13.6

 ユーザー認証系のテスト
 ✓ ログインのテスト
 ✓ ログアウトのテスト


 2 passed (23s)
```

# <font color="LightSeaGreen">業務で使い込んで知ったTestCafeのつらみ（単なる愚痴です）</font>
- **テストを動作させる環境のマシンスペックにテスト通過の可否が左右される**
  - これはかなりハマった要因なのですが、ログインのテストなど割と重めの処理のテストを実行しようとすると低スペックマシンだとテストが通過しません。低スペックマシンといっても使っていたのはwindows機のメモリ8GB、CPU intel core i3のマシンなのでそこまで悪くはないのですが...。
  - e2eテストはテストを行う時に、プロキシサーバを構築してそこからテストを行うようになっているのでかなりマシンにかかる負担が重いようです。今回cypressも試していて同様の現象が発生したので、どうやらtestcafeだけでなくe2eテスト自体低スペックマシン？では荷が重いようです。
  - 弊社では、circlecI上でもマシンスペックを最大（X-large CPU 8、メモリ 16GB）にして実行しています。それ以下だと普通に落ちます。
- **参考となる情報が少ない（日本語情報が特に）**
  - testcafeを使っている人口が少ないからか、ネット上の情報は少ない方だと思います。公式ドキュメントはかなりしっかりと作られているので、読み込めばいいのですが、他の人の体験談や苦労話もあったらいいのにと思うことは多々あります。
- **サポートの対応があっさりしている**
  - 問題が発生したときにtestcafeフォーラムで同様の原因を探し、同じような事例を見つけても「サポート側で試してみたけど再現しませんでした、testcafeのバージョンを上げてください」みたいな対応が多く、結構投げやり感があるなぁと思います。（まあオープンソースなので文句は言えませんが...）

# <font color="LightSeaGreen">Cypressの特徴</font>
> [cypress](https://www.cypress.io/)
> The web has evolved. Finally, testing has too.
> Fast, easy and reliable testing for anything that runs in a browser.

### メリット

- テストが高速
- ブラウザのGUIがとても使いやすい
デバッグがしやすい
- テスト結果を動画で残しておける

### デメリット

- マルチブラウザでテストができない。chrome指定。

# <font color="LightSeaGreen">Cypressを動かしてみる</font>

testcafe同様、cypressモジュールをdevにインストールします。

```shell
$ npm i -D cypress
```

上記でtestcafeで行ったテストと全く同様のテストをcypressにて記述します。
testcafeと違いasync/awaitを使わずとも、裏で待ちの処理を入れてくれるため、シンプルにコードを記述することができます。
テストファイルの場所は固定で、cypressをインストールした時に自動で作成されるcypressフォルダの中にある`integration`ファルダ内にテストコードを作成していきます。

```typescript:cypress/integration/app.spec.ts（一例）
describe('page transition', () => {
  const baseUrl = Cypress.env('baseUrl');

  // 全てのテストに対して事前処理を記述
  beforeEach(() => {
    cy.visit(baseUrl);
    cy.get('input[data-test="username-input"]').type('test'); // Domを指定して入力
    cy.get('input[data-test="sign-in-password-input"]').type('test1234');
    cy.get('button[data-test="sign-in-sign-in-button"]').click(); // Domを指定してクリック
  });

  // テストケースを記述
  it('ログインのテスト', () => {});

  it('ログアウトのテスト', () => {
    cy.get('button[data-test="sign-out-button"]').click();
  });
});

```

テストコードを記述し終わったら、以下のコードでcypressGUIを起動します。（結構リッチなUIですね！）
実行するテストをクリックすることで、自動でテストを動作させます。もちろん全てのテストを実行ボタンもあります。

```
$ npx cypress open
```
testcafeと同じテストをしているのですが、1/4~1/3程度時間でテストが完了しました。testcafeと比べて、めちゃくちゃ早く感じます。
![938af2869acf74a4f2c5cfd36cee10be.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/f728f104-63ab-0d20-1724-4f22c5bd680a.gif)




# <font color="LightSeaGreen">TestCafeとCypressの比較</font>
まずは、`npm trends`でダウンロード数の比較をしてみます。cypressが圧倒的に高いですね。testcafeを愛用している身からすると少し残念ですが、今回cypressのコードを書いたり、実際に動かしてみて納得の結果かなと思います。
![2be9a3ef286f545ae998d305f5d8d612.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/aef04110-b76f-b1b5-65ee-4fe1f68a94ad.png)

ただし、マルチブラウザテストを行う上では、cypressを使うことはできないので、testcafe一択なのかなと...。
Remoteで動かせるのも、Windows・Mac両方の確認をする際に効率良く作業を行うことができます。

まとめるとこんな感じですかね。

### TestCafe
マルチブラウザテストがどうしても必要。Windows・Mac・Ipadなどでもしっかりテストを行いたい。
コードのハンドリングはものすごく大変でも仕方ない。

### Cypress
マルチブラウザでのテストは必要なく、最低限の疎通確認だけでいい。
テストコードをシンプルに書きたいし、デバッグも簡単にしたい。テストを高速で終わらせたい。


# <font color="LightSeaGreen">CircleCIでE2E自動テストをCI化してみる</font>
> [CircleCI](https://circleci.com/)
> We build CI/CD so you can build the next big thing.
> Automate your development process quickly, safely, and at scale.

## CircleCI導入

circleciの利用方法は簡単で、ざっくりと以下の手順で導入することができます。

1. circleciのurlにアクセスし、`Github`もしくは`Gitbucket`のアカウントでサインアップ
2. ログイン後、ダッシュボードから`ADD PROJECTS`を選択
3. CIを実行したい、リポジトリを選択（`Set Up Project`ボタンを押す）
4. githubで管理している対象のリポジトリルート配下に`/.circleci/config.yml`を追加
5. config.ymlにCI上で実行したいスクリプトを記述
6. githubに上記ファイルをpush（どういった「アクション」をCIを動かすためのトリガーにするかは、自由に設定できます）

## config.ymlにスクリプトを記述
今回は、「並列処理」や「特定のブランチpush時に起動」など細かい制限なしに、単純にgithubにpush時にテストコードが自動で動作し、結果がわかるというスクリプトを記述しています。また、実験のためtestcafeとcypress両方で同じ内容のテストコードを実行していますが、本来は一方だけで問題ありません。

```yml:/.circleci/config.yml
version: 2.1
jobs:
  build:
    docker:
      - image: circleci/node:12.10.0-buster # nodeがインストールされたubuntu bionicのdockerイメージをpull
    steps:
      - checkout # dockerコンテナ上に、githubのリポジトリデータをコピー
      - run: curl https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add - # 以下、テスト用ブラウザとしてgoogle chromeをubuntuにダウンロード+インストール
      - run: echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' | sudo tee /etc/apt/sources.list.d/google-chrome.list
      - run: sudo apt-get update
      - run: sudo apt install google-chrome-stable
      - run: sudo apt-get install libgconf-2-4
      - run: npm i # node module をインストール
      - run: CI=true npm run test # jest単体テストを回す
      - run: sudo npm i forever -g # foreverモジュールを利用してバックグラウンドでreactアプリを起動できるようにする
      - run: forever start -c "npm start" ./
      - run: npx cypress run --spec "cypress/integration/app_light.spec.ts" # cypressテストを実行
      - run: npx testcafe chrome:headless testcafe/test1_light.ts # testcafeテストを実行
```

## 実行してみる、ダッシュボードの実行結果
ブラウザ上のcircleciのダッシュボードで結果をみることができます。コマンド1つ１つに対してログを見ることができるため、わかりやすいです。もし通信状況などでテストが失敗してしまっても、`Rerun workflow`から再実行することも可能です。

![681f21231535d672cbf8b3db0d76a4e6.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/b68c1e6f-1016-ef6a-3c58-ad5ffd33d46d.gif)



# <font color="LightSeaGreen">GithubActionsでE2E自動テストをCI化してみる</font>

> [GitHub Actions](https://github.co.jp/features/actions)
> アイデアからリリースまでのワークフローを自動化
> GitHub Actionsを使用すると、ワールドクラスのCI / CDですべてのソフトウェアワークフローを簡単に自動化できます。 GitHubから直接コードをビルド、テスト、デプロイでき、コードレビュー、ブランチ管理、問題のトリアージを希望どおりに機能させます。

## Github Actions導入

github actions はgithubのアカウントを持っていれば、速攻利用することができます。CIを実行したい、リポジトリのルート配下に`.github/workflows/{ 任意のファイル名 }.yml`を作成し、スクリプトを記述後githubにpushするだけで、CIワークフローが動作します。

## ymlにスクリプトを記述
今回はわかりやすく`continuous-integration-workflow.yml`（公式サイトの例と同じです..）と命名したymlファイルにスクリプトを記述して行きます。前提条件は上記で記述したcircleciと同一です。

```yml:/.github/workflows/continuous-integration-workflow.yml
name: Testing
# This workflow is triggered on pushes to the repository.
on: [push] # CIの実行トリガーはgithubにpushされた時

jobs:
  container-job:
    name: Testing
    runs-on: ubuntu-18.04 # ubuntu bionicのdockerイメージをpull

    steps:
      - uses: actions/setup-node@v1 # nodeをインストール
        with:
          node-version: 12
      - uses: actions/checkout@v1 # dockerコンテナ上に、githubのリポジトリデータをコピー
      - run: curl https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add - # 以下、テスト用ブラウザとしてgoogle chromeをubuntuにダウンロード+インストール
      - run: echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' | sudo tee /etc/apt/sources.list.d/google-chrome.list
      - run: sudo apt-get update
      - run: sudo apt install google-chrome-stable
      - run: npm i # node module をインストール
      - run: CI=true npm run test # jest単体テストを回す
      - run: npm i forever -g # foreverモジュールを利用してバックグラウンドでreactアプリを起動できるようにする
      - run: forever start -c "npm start" ./
      - run: npx cypress run --spec "cypress/integration/app.spec.ts" # cypressテストを実行
      - run: npx testcafe chrome:headless testcafe/test1.ts # testcafeテストを実行
```

## 実行してみる、ダッシュボードの実行結果
ダッシュボードは非常にシンプルです。ワークフローの再実行やコンテナの並列化など必要な機能は全て搭載されています。ただ、github actions自体2019年11月に正式版がリリースされた新サービスのため、どうしてもcircleciのダッシュボードと比べると見劣り感はありますね..。ただ、circleciではできないgithubのいろいろな機能を自動化できるようです。もっと色々と試してみる必要がありそうです。

![b23d2865310219cde019bd587e28003a.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/d6d6cc25-3a4c-3c2f-b975-c9f061932109.gif)

# <font color="LightSeaGreen">CircleCIとGithubActionsの比較</font>
まずは共通項として、ymlファイルに記述するするコードは指定名などに若干差異があるものの、互いに流用できるレベルだなと思いました。というわけで、乗り換えるのはそこまで苦ではないのかなといったイメージです。
それでは、相違点について料金プランと利用条件・利用環境（リソース）などの観点からまとめてみました。

## CircleCI

### フリープラン
- 実行環境`Windows/Linux`限定
- 同時実行ジョブは`1`ジョブのみ
- 週`2500`クレジット付与（デフォルトレート（`Docker/Linux Medium`）で1分につき`10`クレジット=`250`分実行可能）
- ワークフロー実行マシンリソース `Docker/Linux Medium`の場合、vCPU `2`、メモリ `4`GB
- ワークフロー実行マシンリソース `Windows Medium`の場合、v CPU `4`、メモリ `15`GB（ただし1分につき`40`クレジット消費=約`62`分実行可能）

### 有料プラン
- 月`30`$~（使用量に対して変動）
- 実行環境Windows/Linuxに加え`Mac`も可能
- 同時実行ジョブは`オートスケール（無制限）`
- 週`25000`クレジット付与
- ワークフロー実行マシンリソース最大 `Docker/Linux 2 X-large+`の場合、v CPU `20`、メモリ `40`GBまで可能

> [CircleCI料金プラン](https://circleci.com/ja/pricing/)

## GithubActions
料金プラン問わず共通項として

- 実行環境`Windows/Linux/Mac`使用可能
- 1時間に実行できるAPIリクエストは、1つのリポジトリの全アクションで最大`1000`まで
- ワークフローの各ジョブは、最大で`6`時間まで実行できる
- ワークフロー実行マシンリソース CPU `2`、メモリ `7` GB（調査した限り現状スケールできない）

料金プランが複数あったため表形式でまとめました。

料金プラン |料金（1ユーザーに対し月）| 合計同時実行ジョブ数 | macOSの最大同時実行ジョブ数
----------- |----------- | --------------------- | -----------------------------
無料          |$0| 20                    | 5                            
Pro         |$7| 40                    | 5                            
Team        |$9| 60                    | 5                            
Enterprise  |$21| 180                   | 15                           

> [GithubActions料金プラン](https://github.co.jp/pricing)

# <font color="LightSeaGreen">まとめ</font>
今回testcafeとcypressを比較してみて、「隣の芝生は青く見える」ではないですが、cypressでのe2eテストコード開発はかなり快適に感じました。（業務で使うと色々欠点が見えてくると思いますが...）。ただ、弊社のクライアント様はWindowsIE・Edgeなどを使っているケースも多々あるため、マルチブラウザテストは必須かと考えています。そういった観点で、現状はtestcafeを使い続けるのがベストな手段かなと思います。2020年chromiumベースのEdgeリニューアルとIEの廃止が囁かれているので、それが現実になったら乗り換えもありかなと。
circleciとgithub actionsに関しては、github actionsは調査した限り、ワークフロー実行マシンリソースのスケールができないようなのでe2eテストを実行する用途ではまだ難しいのかなというのが実感です。ダッシュボードに関しても、circleciの方が使いやすい印象を抱きました。
ただ、CIをgithub内で完結できるシンプルさと、github機能を自動化できるところはgithub actionsでしかできないことなので、今後さらに調査を深めると同時に目が離せない機能であることは間違いないと考えています。

テストコードは最初は面倒だと感じてしまう部分もありますが、自分のたちをコードを守るという意味でも重要な位置づけであることは間違いないです。また、テストコードを書くうちに、テストが全て通った瞬間+自動テストフローをうまく組めた瞬間に快感を覚えるようになってくると思います。上記で解説したように、テストコードを書く環境もかなり整ってきているので、ツールを活用しながら、よりよい開発環境構築を目指して行きたいです。