Node.js
Selenium

E2Eテストの導入から学んだこと

こちらはDMM.com #1 Advent Calendar 2017 16日目の記事です。
前日の記事は@daichiiiさんの快適なMarkdown編集環境でした。

カレンダーのURLはコチラ
DMM.com #1 Advent Calendar 2017
DMM.com #2 Advent Calendar 2017

はじめに

皆さんが開発しているWebシステムはE2Eテストをとりいれていますか?
今回は会員フロントエンドチーム(ログイン/会員登録機能を提供しているチーム)のE2Eテストの失敗談を共有します。
一般的なE2Eテストの失敗談や、ログイン画面、登録画面ならではのつらかったポイントを挙げていきますので、何か1つでも参考になればと思います。

対象読者

  • 初めてプロジェクトにE2Eテストを取り入れようと考えているかた
  • ログインページ、登録ページでE2Eテストを取り入れようと考えているかた
  • 不安定なE2Eテストに悩まされているかた

目次

まずは、チームの現状をお話する前に、そもそも「E2Eテストとはなにか?」というのを自分なりに調べたのでまとめます。
あるとき、E2Eテストが不安定になって苦しい状況になったので、改めて書籍を買って調べました。(もっと早く調べておけば…)
参考にした文献、Webサイトは参考リンクに記します。

E2Eテストとは

E2E(End to End)Testは、User Interface Testとも呼ばれ、システム全体を通してテストをおこないます。
Webサービスの場合は、ユーザと同じようにブラウザを操作し、挙動が期待通りになっているか確認します。
例えばログインページのテストシナリオの1つは以下のようになります。

  1. ログイン画面を表示
  2. メールアドレスを入力
  3. パスワードを入力
  4. ログインボタンを押下
  5. トップページに遷移していること

特徴

  • 上位レベル
  • フィードバックが遅い
  • 実行が遅い
  • 壊れやすい

といったユニットテストとは逆の特徴があります。
よく語られるのが以下のようなピラミッドの図です。
上に行くほど、遅く、コストが高いと言われます。
test-pyramid.png
https://martinfowler.com/bliki/images/testPyramid/test-pyramid.png

なぜ不安定になるか

E2Eテストが不安定になるには様々な理由があります。
ユニットテストと異なり、ネットワークアクセス、DBアクセス、ファイルアクセスなど、外部通信が発生するのでそれらの影響を受け、成功していたテストが失敗したりします。
また変更の影響も受けやすく、例えばWebシステムの場合、デザインの変更などで今まで通っていたテストが失敗することがあります。

不安定なE2Eテストがもたらす悪影響

  • 修正に時間がかかる
    実行のたびに何処かでテストが失敗するとそれを修正する手間がかかります。 たとえば通信待ちでテストがタイムアウトした場合、そのテストケースを実際に実行してみて、テストが通るような待ち時間を設定する必要があります。 また、安定していたと思ったテストは別の環境で実行すると、その環境の外部要因でまた失敗することがあります。
  • テストが落ちていても無視するようになる
    テスト結果が不安定なままになっていると、そのうち失敗している状態が重要視されないようになります。 これにより、本当にシステムに問題が起きた時に気づくのが遅れます。 かならず失敗するテストは直せばよいだけなので、不安定なテストはかならず失敗するテストよりも悪い状態です。

不安定なE2Eテストに対する対処法

  • テストを書き直す
    テストシナリオが正しいか、テストの記述方法に改善点(使用するメソッド、テストプログラムの設計など)はないか確認します。
  • テストをピラミッドの下の層へ移動させる
    ユニットテストはフィードバックも早く、安定的に動作します。 なにもかもE2Eテストでカバーせずに、ユニットテストで機能を担保できないか検討します。
  • 価値のないテストとみなし、テストを止める
    全てのユースケースの自動テストを安定的に機能させるのは、作業コストが大きくなります。 不安定な場合は潔く諦めて、手動で検証することを検討します。

次に、E2Eテストを運用している現状を記します。

チームの現状

テスト実施環境

言語:JavaScript
ブラウザオートメーション:WebdriverIO
最初はNightmareを使っていましたが、マルチブラウザ対応のために変更しました。
テストフレームワーク:Mocha
OS:macOS
ブラウザ:Firefox,Google Chrome
実施タイミング:リリース前に検証環境で実施

テストの結果

あるときに実行したテスト結果は以下のようになりました。

  • テストケース62件
  • テストファイル16個
  • 実行時間26分
  • テスト通過率51/62

つらいポイントと今後の対策案

E2Eテストを運用するうえで苦しんでいるポイントと、現時点の対策案を書きます。

テストの実行時間が長い

62件のテストケースを実行するのに26分かかっています。
ユニットテストと比べると時間がかかるのはしょうがないことですが、できる限り短くしたいところです。
対策案

  • テストを並列実行する
  • 削減できるテストはないか検討する
  • テストの記述方法を改善して成功率を上げる(実行時間はテストのリトライも含めています。一度で成功したらその分実行時間が短くなります)

並列実行は以下の2案です。
並列化の案1:
maxInstancesオプションを使います。
https://github.com/webdriverio/webdriverio/blob/master/examples/wdio.conf.js#L52-L60
並列化の案2:
CircleCI Enterpriseでコンテナを並列実行します。
実行時にシェルスクリプトでコンテナ毎に均等にテストファイルを割り当てます(ファイル16個/コンテナn個)。

テストが安定しない

以下のような理由でテストが安定していませんでした。

すべてのユースケースをテストしようとしていた

ユーザシナリオと検証項目をまとめた資料があり、それを全てテストケースに書き起こそうとしていました。
対策案
不安定なE2Eテストへの対処方法に従い、クリティカルなケースをテストすることを優先します。
複雑なテストケースは手動で検証できないか検討します。

テストケースでアカウントを共有していた

これにより、
「ログイン状態であることをテストしようとしたら、別の人が実行しているテストでログアウトしていて失敗する」
「登録できることをテストしようとしたら、別の人が実行しているテストですでに登録しているので失敗する」
というようなことが起こりました。
テストデータを共有しているとテストを並列実行できなくなります。
対策案
実行環境+テストケース毎にユーザアカウントを分離します。
これにより、誰でも好きなときにテストを実施できます。
アカウントIDにタイムスタンプをつけてテスト実行毎にユニークなアカウントを作成している事例もあるようです。

リダイレクトが多くて失敗していた

わたしたちのE2Eのテストケースはサイトの特性上、リダイレクトすることが多いのですが、リダイレクトが完了する前にassertionしてしまっていて、テストに失敗することがありました。
待ち時間を伸ばしてみても、どこかの部分のリダイレクトが遅れることで、それ以上の時間がかかってテストに失敗することがありました。
対策案
不安定なE2Eテストへの対処方法に従い、テストの見直しと、テストケースを削ることを検討します。

フィードバックサイクルを整備するのが遅かった

現在、E2Eテストは手動で実施している状況です。具体的には、リリース担当者がリリース前に検証環境で実行し、その結果を記録するというかたちを取っています。
対して、ユニットテストはGitHub Enterprise、CircleCI Enterprise、Slackを連携させることで、コミットしたらテストが走り、その結果をSlack通知しています。
本来ならば、ユニットテストと同様に、E2Eテストもフィードバックサイクルを作ってからテストの記述を進めるべきでした。しかし、タスクの関係で遅くなった結果、いくつかのテストを書いてからCI環境を整えることになりました。
これにより、多くの不安定なテストをCircleCIコンテナへ移行することになりました。その結果、ローカルでは動いていたのに、CircleCIコンテナ上ではテストが通らないというようなことがおこりました。このような状況になると、テスト自体が不安定なのか、それともCircleCIコンテナに移行したことで失敗しているのか、原因の切り分けが困難になります。また、テストが多いと終わるまでに時間がかるので、正しく連携できているか設定を確認するのに時間がかかりました。
対策案
今からでも遅くないので、ユニットテストと同様にフィードバックサイクルを整備します。

工夫している点

つらかった点も多いですが、工夫している点もあるので、いくつか挙げます。

Page Object Pattern

E2Eテストはデザインの変更を受けやすいという特徴があります。
もし、テストケースに直接セレクタを記述する場合、デザインの変更があった場合、セレクタを記述している全てのテストケースを修正しなければなりません。
しかし、ページをオブジェクトとしてとらえ、セレクタを隠蔽することで、デザインの変更があった場合も修正を最小限に抑えることができます。
cf.http://webdriver.io/guide/testrunner/pageobjects.html
cf.https://github.com/SeleniumHQ/selenium/wiki/PageObjects

waitFor*メソッド

待ち時間を設定するときに、最初はpauseメソッドを使っていましたが、ドキュメントにもある通り、waitFor*メソッドを使用するように変更しました。これにより実行時間を少しでも減らすことができます。

In order to avoid flaky test results it is better to use commands like waitforExist or other waitFor* commands.

テストが落ちても再実行する

不安定になることを考慮して、失敗した時にテストを再実行しています。
mochaのretryオプションを使用しています。
ドキュメントにもあるようにE2Eテストのための機能です。

This feature is designed to handle end-to-end tests (functional tests/Selenium…) where resources cannot be easily mocked/stubbed.

WebdriverIOにも同様のドキュメントがあります。
cf.http://webdriver.io/guide/testrunner/retry.html
ただし、mochaのretryオプションはbefore,afterなどのセクションはretryしてくれないので注意が必要です。
cf.https://github.com/mochajs/mocha/issues/2120

今後の展望

足りない機能が沢山あるので現状以下のようなことがTODOとして上がっています。

  • テスト結果のSlack通知
    ユニットテストは、すでにCircleCIで実行したテスト結果をSalckに通知しているので、同様に通知する予定です。
  • ChatOps化
    CircleCIと連携したあとはSlackのslash commandでテストを実行できるようにする予定です。
  • テストケースのグループ分け
    mochaのtaggingを使用して実行するテストをフィルタリングできるようにします。 例えばクリティカルな機能にのみタグを付けて、限定してテストを実施できるようにします。

まとめ

今回4か月ほどE2Eテストを運用してきて以下のような学びを得ました。

  • テストピラミッドを意識して、テストの実装方針を決める
    テストケースのコストを見極め、E2Eテストを記述するべきか取捨選択することが大切だと感じました。並列実行するのか、アカウントの扱いをどうするかなども先に決めておく必要があると思いました。
  • フィードバックサイクルを先に整備する 
    あとからフィードバックサイクルを作ろうとすると大変なので、早い段階で環境を整備することが大切だと感じました。

つらかったポイントが多いですが、E2Eテストにより不具合に気づけた事例も数件ありました。
これからもE2Eテストの環境を改善していこうと考えています。

コストが高く、手を抜かれがちなE2Eテストですが、方針を固めて適切に実装することで、プロジェクトの助けになります。
みなさんも取り入れてみてはいかがでしょうか?

明日は@hayatanさんの「3年前、はじめて開発したサービスアプリに今からCleanArchitectureを導入してみたい話。」です!

参考文献

以下、記事を書くにあたり参考にした書籍や記事のリンクです。
https://www.oreilly.co.jp/books/9784873118161/
https://martinfowler.com/bliki/TestPyramid.html
https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html
https://testing.googleblog.com/2016/05/flaky-tests-at-google-and-how-we.html

後日談

以下の記事で後日談を追記しました。
https://qiita.com/mt0m/items/71f1e97828a1dc06bc93