アドベントカレンダー私が担当する最後の記事は、アプリの品質を保つ上で最も重要なテスト
についてです。
以下の記事の続きという前提なので、React
+AWS
で作成したWebアプリ(SPA)に自動テストのフローを追加するという流れで今回の解説を行います。
テスト
と聞くと「あんまやる気でねー」となるかもしれませんが、テストがないとリリース時に常に肝を冷やしながら作業をするはめになるので、そんなリスクを犯すくらいならテストしっかり書いておくことで、精神安定剤のかわりになってくれます。
そんなわけで、弊社では自動テストコードをしっかり書いて、作業時・リリース時に必ず回すというフローに対してかなり重きをおいています。1回テストコードを書いておけば、繰り返し使いまわせますしね。
テストと一言で言っても、単体テスト
・結合テスト
・機能テスト
・受け入れテスト
など様々な種類がありますが、本記事で取り上げるのは本番環境に近い形でアプリケーションを起動し、UIを操作しながら正しい挙動をするかどうかを確認するE2E(インテグレーション)テストについてです。
弊社では、circleci
とtestcafe
を組み合わせてe2eのテスト自動化
+CI化
を図っているので、そこで得た知見とつらみ、類似サービスで気になっていたgithub actions
・cypress
も使いながらベストプラクティスを模索していきます。
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 でパスを指定すると動作させることができます。
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"]');
});
$ npx testcafe chrome testcafe/test1.ts
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
ファルダ内にテストコードを作成していきます。
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と比べて、めちゃくちゃ早く感じます。
TestCafeとCypressの比較
まずは、npm trends
でダウンロード数の比較をしてみます。cypressが圧倒的に高いですね。testcafeを愛用している身からすると少し残念ですが、今回cypressのコードを書いたり、実際に動かしてみて納得の結果かなと思います。
ただし、マルチブラウザテストを行う上では、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の利用方法は簡単で、ざっくりと以下の手順で導入することができます。
- circleciのurlにアクセスし、
Github
もしくはGitbucket
のアカウントでサインアップ - ログイン後、ダッシュボードから
ADD PROJECTS
を選択 - CIを実行したい、リポジトリを選択(
Set Up Project
ボタンを押す) - githubで管理している対象のリポジトリルート配下に
/.circleci/config.yml
を追加 - config.ymlにCI上で実行したいスクリプトを記述
- githubに上記ファイルをpush(どういった「アクション」をCIを動かすためのトリガーにするかは、自由に設定できます)
config.ymlにスクリプトを記述
今回は、「並列処理」や「特定のブランチpush時に起動」など細かい制限なしに、単純にgithubにpush時にテストコードが自動で動作し、結果がわかるというスクリプトを記述しています。また、実験のためtestcafeとcypress両方で同じ内容のテストコードを実行していますが、本来は一方だけで問題ありません。
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
から再実行することも可能です。
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と同一です。
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のいろいろな機能を自動化できるようです。もっと色々と試してみる必要がありそうです。
CircleCIとGithubActionsの比較
まずは共通項として、ymlファイルに記述するするコードは指定名などに若干差異があるものの、互いに流用できるレベルだなと思いました。というわけで、乗り換えるのはそこまで苦ではないのかなといったイメージです。
それでは、相違点について料金プランと利用条件・利用環境(リソース)などの観点からまとめてみました。
CircleCI
フリープラン
- 実行環境
Windows/Linux
限定 - 同時実行ジョブは
1
ジョブのみ - 週
2500
クレジット付与(デフォルトレート(Docker/Linux Medium
)で1分につき10
クレジット=250
分実行可能) - ワークフロー実行マシンリソース
Docker/Linux Medium
の場合、vCPU2
、メモリ4
GB - ワークフロー実行マシンリソース
Windows Medium
の場合、v CPU4
、メモリ15
GB(ただし1分につき40
クレジット消費=約62
分実行可能)
有料プラン
- 月
30
$~(使用量に対して変動) - 実行環境Windows/Linuxに加え
Mac
も可能 - 同時実行ジョブは
オートスケール(無制限)
- 週
25000
クレジット付与 - ワークフロー実行マシンリソース最大
Docker/Linux 2 X-large+
の場合、v CPU20
、メモリ40
GBまで可能
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 |
まとめ
今回testcafeとcypressを比較してみて、「隣の芝生は青く見える」ではないですが、cypressでのe2eテストコード開発はかなり快適に感じました。(業務で使うと色々欠点が見えてくると思いますが...)。ただ、弊社のクライアント様はWindowsIE・Edgeなどを使っているケースも多々あるため、マルチブラウザテストは必須かと考えています。そういった観点で、現状はtestcafeを使い続けるのがベストな手段かなと思います。2020年chromiumベースのEdgeリニューアルとIEの廃止が囁かれているので、それが現実になったら乗り換えもありかなと。
circleciとgithub actionsに関しては、github actionsは調査した限り、ワークフロー実行マシンリソースのスケールができないようなのでe2eテストを実行する用途ではまだ難しいのかなというのが実感です。ダッシュボードに関しても、circleciの方が使いやすい印象を抱きました。
ただ、CIをgithub内で完結できるシンプルさと、github機能を自動化できるところはgithub actionsでしかできないことなので、今後さらに調査を深めると同時に目が離せない機能であることは間違いないと考えています。
テストコードは最初は面倒だと感じてしまう部分もありますが、自分のたちをコードを守るという意味でも重要な位置づけであることは間違いないです。また、テストコードを書くうちに、テストが全て通った瞬間+自動テストフローをうまく組めた瞬間に快感を覚えるようになってくると思います。上記で解説したように、テストコードを書く環境もかなり整ってきているので、ツールを活用しながら、よりよい開発環境構築を目指して行きたいです。