Help us understand the problem. What is going on with this article?

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

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

AWS Amplify / AppSyncで画像投稿webアプリのサーバサイドを実装する

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

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

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

TestCafeの特徴

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が使いづらい デバッグがしにくい

TestCafeを動かしてみる

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

$ npm i -D testcafe

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

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"]');
});

chromeでテストする場合
$ npx testcafe chrome testcafe/test1.ts

a99584eb492ff7141f05b39a2a9e8ae5.gif
3ddfed61192c4f2d147771d7932528c7.gif

Running tests in:
 - Chrome 79.0.3945.88 / macOS 10.13.6

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


 2 passed (23s)

業務で使い込んで知ったTestCafeのつらみ(単なる愚痴です)

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

Cypressの特徴

cypress
The web has evolved. Finally, testing has too.
Fast, easy and reliable testing for anything that runs in a browser.

メリット

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

デメリット

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

Cypressを動かしてみる

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

$ npm i -D cypress

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

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

TestCafeとCypressの比較

まずは、npm trendsでダウンロード数の比較をしてみます。cypressが圧倒的に高いですね。testcafeを愛用している身からすると少し残念ですが、今回cypressのコードを書いたり、実際に動かしてみて納得の結果かなと思います。
2be9a3ef286f545ae998d305f5d8d612.png

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

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

TestCafe

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

Cypress

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

CircleCIでE2E自動テストをCI化してみる

CircleCI
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両方で同じ内容のテストコードを実行していますが、本来は一方だけで問題ありません。

/.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つ1つに対してログを見ることができるため、わかりやすいです。もし通信状況などでテストが失敗してしまっても、Rerun workflowから再実行することも可能です。

681f21231535d672cbf8b3db0d76a4e6.gif

GithubActionsでE2E自動テストをCI化してみる

GitHub 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と同一です。

/.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

CircleCIとGithubActionsの比較

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

CircleCI

フリープラン

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

有料プラン

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

CircleCI料金プラン

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料金プラン

まとめ

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

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした