本記事は『テスト自動化実践ガイド 継続的にWebアプリケーションを改善するための知識と技法』(著:末村拓也)の「第4章 E2Eテストとは何か」から一部を抜粋したものです。掲載にあたって編集しています。
E2Eテストの目的と責務
E2Eテストの特徴について、詳しく説明しておきます。もちろん、E2Eテストをどのように定義するかは開発チームによって変わってきます。以下の説明は、あくまで『テスト自動化実践ガイド』における定義として理解してください。
- E2Eテストで使用するインターフェースはユーザーインターフェースになります。システムとしてユーザーに提供するものをテストするので、これはある意味当然ともいえるでしょう
- E2Eテストのテストベース、つまりテストケースのもとになるのはユーザーストーリーです。ユーザーストーリーとは、ユーザーがその機能を用いて達成したいことです。ただし、テスト対象のソフトウェアのテスタビリティ(テスト容易性)が低く、利用できるテストレベルが限定されるケースなど、技術的制約などによってそれ以外のものをテストすることもあります
- E2Eテストのテスト対象は完全に統合されたシステム全体です。「システム全体」が何を表すかはシステムの大きさにもよりますが、たとえばマイクロサービスアーキテクチャを採用するチームの場合、すべてのマイクロサービスが揃った完全な状態での動作を確認するためにE2Eテストを利用することがあります
- E2Eテストで想定されるバグは「ログインできない」「商品がカートに入らない」など、ユーザーストーリーそのものの失敗です
E2Eテストのアップサイドとダウンサイド
テストレベルとしてのE2Eテストの特徴は前節に挙げたとおりですが、それ以外の特徴についてもアップサイドとダウンサイド、両面を見ていきましょう。ここでは特に、ブラウザやモバイルデバイスなどによるGUIを提供するシステムに関するものを取り上げます。
幅広い用途に利用できる
E2Eテストの最も有力なアップサイドとしては、その用途の幅広さでしょう。他のテストレベルでは難しい様々なテストに利用できます。
たとえば互換性テストとして、複数のブラウザ、OS、デバイスを利用したテストを行うことがあります。パターンが増えれば、テストケースの数は何倍にも膨れ上がります。手動でのテスト実施が非現実的なケースも多いでしょう。
ただし、古いデバイスやブラウザなどは、自動化ツール側でサポートされていなかったり、テスト実行時に不可解なエラーが発生することも多く、手動テストと同様に自動テストもやっかいなことがよくあります。
E2Eテストは仕様化テストとして利用されることもあります。仕様化テストとは、自動テストが存在せず、仕様書すらも残っていないようなアプリケーションに対して、現在の振る舞いを正しいものとして作成するテストコードのことです。そもそもソフトウェアがテスト可能な設計になっていないようなときに、最初に書くテストとなることが多いでしょう。
もちろんE2E以外のテストレベルでも可能ですが、UIを操作点・観測点として利用するというE2Eテストの特性上、利用されることが多いです。他のテストレベルでは、テスト可能な設計になっていない(操作点・観測点が存在しない)こともありますが、E2Eテストは最低でもユーザーが実行できるレベルの操作は可能なためです。
もちろん、現在のソフトウェアの振る舞いを単にテストに起こすということは、バグがある状態でテストコードが書かれてしまう可能性もあります。とはいえ、最初の一歩としては十分すぎるものですし、その後の改善に大いに役立ちます。
あるいは、生きたドキュメントとして利用できる場合もあります。ユーザーにとって、このソフトウェアはどのように操作するものなのか、どのように価値を生み出すものなのかを説明し、それが達成されることを常に検証し続けてくれます。もし振る舞いが変わった場合には、テストが失敗して教えてくれて、常に最新のドキュメントを提供してくれます。このような考え方は、受け入れテスト駆動開発(ATDD)などでよく利用されます。
少し毛色の異なるものとしては、監視に使われることも多いでしょう。システムの監視といえば、データベースやWebサーバーへのコネクション数(接続数)などを思い浮かべる人も多いかもしれませんが、他にも、E2Eテストを実行してそのテストが成功・失敗するかを監視することもあります。これはシンセティックモニタリング(synthetic monitoring)と呼ばれる手法で、ユーザーレベルで本番環境がアクセシブルかどうかをテストする監視手法です。
シンセティックモニタリングで利用されるのは、一般的なE2Eテストと同様のブラウザ自動化ツールであることが多いです。ここで実行するのはいわゆるスモークテストと呼ばれる部類のものが多く、その名のとおり、システムを実行して“煙が出ないか”(目に見えてわかるバグが出ていないか)をチェックするレベルのシンプルなテストを行います。ツールによっては、たとえば、特定の国からアクセスできないといった、アラートを出してくれるものもあります。
ユーザーストーリーそのものをテストできる
もう1つのアップサイドとして、ユーザーストーリーをそのままテスト可能な、ほぼ唯一のテストレベルであることが挙げられるでしょう。
この章の冒頭で述べたように、E2Eテストはユーザーインターフェースを用いてユーザーストーリーをテストします。これが実現できるのはE2Eテストだけです。例外として、たとえばユーザー向けに公開されたWeb APIなどのテストも、ユーザーストーリーのテストといえますが、この場合は、むしろそのWeb APIのテストがE2Eテストでもあると考えられるでしょう。
E2Eテストが想定する「ユーザーストーリーの失敗」というバグは、シナリオとして想定できる中では、およそ最悪のものです。ユーザーは、いつものようにソフトウェアを利用しようとしただけなのに、期待していた機能が動作していないことに気づきます。すぐに大量のクレームにつながるでしょうし、開発者やテスターは「こんな簡単なこともテストしていないのか」というそしりを受けることになるでしょう。
自動化そのものの難易度や複雑性が高い
E2Eテストの大きなダウンサイドとして、自動化そのものに必要な作業が複雑で、それゆえに思わぬ落とし穴にハマることがある、というものが挙げられます。
E2Eテストを自動実行するには、最低でも以下のようなものが必要になります。
- テスト対象のシステムそのもの:基本的にはシステム全体が統合され、実ユーザーが利用するのと同等の環境を整える必要があります
- システムが動作するクライアント:たとえば、ブラウザやモバイルデバイスなどを指します。場合によってはリアルデバイスではなく、エミュレーターなどを用いて代替することもあります
- ユーザーインターフェースを自動操作するオートメーションツール:これらはブラウザなどのクライアントソフトウェア自身が提供している場合もあれば、サードパーティのツールを利用する場合もあります
あなたがテストしたいのは、テスト対象のシステムそのもののはずです。しかし、ときにはクライアントやオートメーションツールのバグに遭遇してしまうこともあります。たとえば、Google Chromeを自動操作するためのChromeDriverというツールがあります。
これはChromium プロジェクトがメンテナンスしているものですが、ソフトウェアである以上、当然バグが含まれることはあります。以下のURL はChromeDriverのバージョン101~103のバグに関するissueですが、日々様々なバグがレポートされています。
Issue 4121: WebDriver command sometimes fails with "unexpected command response"
新しいブラウザ、多種多様なブラウザをテストしたいという欲求とは裏腹に、それら自身が抱える問題により、自動テストだけがうまく動かないといったケースは起こりがちです。ここではChromeDriverを例に取り上げましたが、クライアントがモバイルデバイスなどのシミュレーターやエミュレーターを利用している場合、これらがトラブルを引き起こす可能性もあります。あるいは、セキュリティ設定など、開発中のシステム特有の考慮事項もあるでしょう。
E2Eテストはシステムをテストします。そして、システムは外から利用されて価値を生むものです。別の表現をすれば、E2Eテストは他のテストレベルとは異なり、システムの外に大きく依存するテストです。そのため、実装における考慮事項は非常に多くなります。
テストしやすさに配慮する余地が少ない
テストレベルとしてのE2Eテストの特徴には、「ユーザーインターフェースを用いる」というものがありました。逆にいえば、テストに使えるのはそれしかありません。そもそもユーザーインターフェースはテストのために作られているわけではなく、ユーザーにとって不要な情報は積極的に隠されていることもよくある話です。そのため、E2Eテストの実行中にやむを得ずシステム内部の状態を取得したいような場合では、テストコードの実装に苦慮する場合があります。
たとえば、以下のような仕様のECアプリケーションがあるとします。
- 商品をカートに入れると、在庫が1つ「予約」状態になり、総在庫数から1つ少なくなる
- 在庫数は10個以上の場合は「在庫あり」として表示され、9個以下の場合は実際の在庫数が表示される
このようなケースでは、在庫数が10個以上の場合、1.のケースをE2Eでテストすることは難しいです。総在庫数が1つ減ったことは画面に表示されないためです。総在庫数を確認できる別のユーザー(EC事業者側の管理者)で在庫数を確認したり、在庫数を9個以下にしておいたり、他のユーザーで大量購入してみたりなど、別の方法で確認する必要があります。
また、他のテストレベルでは頻繁に利用される「モック」「スタブ」などの利用も、大きく限定されます。モックとは、あるコンポーネントを置き換えて、そのコンポーネントが呼び出されたことを確認するためのものです。スタブも同様にあるコンポーネントを置き換えて、テストに必要な固定の値を返します。
サードパーティのAPIを利用したテストを行う場合などには、それらのAPIのレスポンスを固定したい場合があります。たとえば、天気予報を提供するWeb APIは明日の天気を提供します。当然、天気は毎日変わるものなので、常に「晴れ」や「雨」のデータを返してくれるスタブAPIを準備するケースが考えられます。
このような形でスタブを利用すること自体は可能ですが、E2Eテストそのものの価値、つまり「完全に統合された状態のシステムのテスト」という特徴を台無しにしてしまう場合もあります。モック、スタブの利用は他のテストレベルでも注意が必要ですが、E2Eテストでは特に気をつけるべきでしょう。
高コスト
最も気をつけないといけないのは、コストが高くつくことです。ここでいうコストとは、時間的なものと金銭的なものの両方を意味します。
時間の面でいえば、E2Eテストは本物のブラウザやモバイルデバイスなどを利用するため、それらの起動やページロードなども含め、長い実行時間がかかります。特に、ネットワーク的に離れた環境に対してテストする場合は、より多くの時間がかかります。また、ログインが必要なシステムでは、すべてのテストケースでログインページを経由する必要があるなど、テストケースの作りによっては相当な時間を要する場合があります。
金銭的な面では、複数の環境や実デバイスを利用するため、それらに比例してコストがかかります。テスト対象のシステムを完全に統合した状態で起動する必要があるため、場合によっては本番環境と同等の環境を準備する必要があります。もちろん、実際に完全に同じ環境を用意するのは非現実的ですが、データベースのパフォーマンスなどは十分なデータ量が入っていないと事前に検証が難しい場合もあります。たとえば、本番環境にのみ存在する大口の顧客でのみパフォーマンスが劣化するようなケースを事前にテストしたいとしたら、同等のデータ量とスペックを持つ環境がないと十分なテストはできないかもしれません。
どのようにE2Eテストを利用するか
これまでの説明で、E2Eテストは様々な用途に利用できつつも、その取り扱いは難しいことがわかってきました。また、様々な用途に利用できるとはいえ、メインの目的はユーザーのユースケースをテストすることで、画面の機能テストなどに利用しすぎるとダウンサイドに苦しむことになります。
E2Eテストは非常に範囲が広く、汎用的に使えるテストレベルです。自動テストなんて考えたこともないような人が最初に扱うものとしても有効ですし、受け入れテスト駆動開発(ATDD)などの高度なプラクティスにも利用できます。しかし、その便利さに依存してしまうと、ここまでで紹介してきたダウンサイドが徐々に目立ってきます。汎用性に依存せず、それぞれのテストレベルでやるべきこと、やるべきでないことを決めるべきです。
ここからは、E2Eテストをどのように利用し、安全な開発に利用するかを実例も交えて紹介していきます。
短期的な戦略と長期的な戦略を分けて考える
自動テストには、よく知られているベストプラクティスとしてテストピラミッドというものがあります。これはテストレベルをどのぐらいの分量に収めると最もテスト実行の効率が高くなるかを表したモデルです。
テストピラミッドとは真逆のバッドプラクティスとしてアイスクリームコーンというものもよく知られています(図1)。これは、極めて少ない量の自動テストと、大量の手動テストに依存した状態です。
このように、開発プロセスが大量の手動テストに依存してしまっているような状況では、その状態から脱却するために最初にE2Eテストによる自動化を行うことがあります。これは短期的な戦略で、まずは「自動テストが少ない」という状況から脱却するための戦略です。この時点では、ユースケースレベルのテストだけでなく、GUI画面を介した機能テストなども多く含みます。
自動テストを増やすうちに、自動テストの実行時間が長期化したり、テストコードのメンテナンスコストがかさんだり、安定しないテスト(Flakyなテスト)に悩まされることも多くなります。一方で、自動テストシナリオを増やす取り組みとして、見つかったバグを防ぐためのテストをE2Eレベルで書いてしまうこともあります。この段階では、アイスクリームの部分が単に自動化されただけでなく、むしろこれまでよりも増えてしまうこともあります。
この段階で、テストを整理しながら、下方向のテストレベルに徐々に分解していく必要があります。これまでE2Eテストでしか見つけられなかったバグや、E2Eテストでしかできないテストを、結合テストや単体テストで見つけるためのプランを考えます。
長期的には、E2Eテストだけでなく、他のテストレベルについても目的と想定されるバグを整理して、複数のレイヤーでバグを捉えていきます(図2)。その上で、「E2Eテストでしか見つけられないバグ」をできるだけ少なくしていきます。
目的と制約を混同しない
E2Eテスト自動化に取り組むときに重要なのが、目的と制約、特に「技術的制約」とを混同しないことです。
E2Eテストでしかできないテストと、E2Eテストでやるべきテストは必ずしも一致しません。たとえば、ブラウザの拡張機能のテストはブラウザに実際にインストールしないと実施できないように思えるかもしれません。しかし、たとえばDOM要素に対して操作する部分をjsdomなどの軽量なブラウザ実装でテストするなど、より低いテストレベルに切り出すことは十分に可能です。
「内部構造の変化が多く、単体テストに不向き」「単体テストに習熟したメンバーがいない」などの理由から、E2Eテストを好むチームもありますが、実行コストやテスタビリティなどのダウンサイドにぶつかることは予想されます。E2Eでテストしたいものと、何らかの制約でやむなくE2Eテストしているものは区別するほうがよいでしょう。
E2Eテストの利用の例
例として、以下のようなアプリケーションを考えてみます。
- Webアプリケーション
- ブラウザ拡張機能を持ち、他のアプリケーションにウィジェットを埋め込む
- ユーザーに対するメール送信の機能を持つ。メール機能は外部のサービスを利用している
- Webアプリケーション部分はある程度テストが書かれているが、ブラウザ拡張機能やメール送信部分はテストされていない
このアプリケーションはMVCアーキテクチャで構築されています。初期の段階では、それぞれのテストレベルは以下のように利用されています。
- 手動テスト:ブラウザ拡張機能の機能テスト、UI各画面の機能テスト、別システム(メール機能)との連携部分の機能テスト、新機能などのテスト
- E2Eテスト:拡張機能などを含まないシンプルなユースケースのテスト
- 結合テスト:サービスクラスや公開/非公開APIのテスト
- 単体テスト:モデルやコントローラーのテスト
この時点でのテストの構造は、アイスクリームコーン型のものになるでしょう。短期的な戦略としては、以下のようにE2Eテストにかなり依存した形を取ります。これは、まずはテストが足りず、手動テストに依存している箇所に自動テストを足していくことを優先しているためです。ただし、別システム(メール機能)については外部サービスであることと、メールクライアントの自動操作が必要となるため、E2Eでも自動テストができません。
- 手動テスト:新機能などのテスト、別システム(メール機能)との連携部分の機能テスト
- E2Eテスト:メール機能を除くユースケースのテスト、ブラウザ拡張機能の機能テスト、UI各画面の機能テスト
- 結合テスト:サービスクラスや公開/非公開APIのテスト
- 単体テスト:モデルやコントローラーのテスト
この時点でのテストの構造はピラミッドの形を取らず、砂時計型のものになるでしょう。この状態から、長期的に分離可能な箇所を洗い出していきます。以下のような箇所が分離可能な候補として挙げられるでしょう。
- 拡張機能の機能テスト
- UI各画面の機能テスト
- 別システム(メール機能)との連携部分
これらを単独で、つまりE2Eテストより低いテストレベルで実施するために、アプリケーション側のテスタビリティを改善していく必要があります。これらはコードの変更を伴うため、E2E自動テストや手動テストで、ある程度の機能性を担保した上で行う必要があります。
また、これらの取り組みの中で、E2Eテストでカバーできる領域がさらに増える可能性もあります。具体的には、別システム(メール機能)の自動化が可能になれば、テスト可能なユースケースがさらに増えます。最終的には、以下のような形になるでしょう。
- 手動テスト:新機能などの探索的テスト
- E2Eテスト:拡張機能やメール機能も含めたユースケースのテスト
- 結合テスト:サービスクラスや公開/非公開APIのテスト、別システム(メール機能)との連携部分の機能テスト、ブラウザ拡張機能の機能テスト、UI各画面の機能テスト
- 単体テスト:モデルやコントローラーのテスト
この時点で、E2Eテストで扱うのはユースケースのテストのみとなり、より低いテストレベルでそれぞれのコンポーネントやモジュールをテストするようになります。
E2Eテストの価値
さて、ここまででE2Eテストの特徴やアップサイド・ダウンサイド、想定される利用方法などについて説明してきました。そして、長期的にはE2Eテストでしかキャッチできないものをできるだけ少なくし、下位のテストレベルに移譲するのがベストプラクティスであると説明してきました。
ここで疑問になるのが、果たしてE2Eテストは本当に必要なのか、ということです。時間的、金銭的にもコストが高くつく可能性のあるE2Eテストを実施する意義はあるのでしょうか。また、E2Eテストのケースはテストピラミッドで表されるように、非常に少ないのが理想的なのでしょうか。
そこで最後に、E2Eテストがもたらす価値について、改めて整理してみたいと思います。
初めに書かれる最も基本的なテストである
何らかの機能を追加したり、バグを修正したりするときには、その変更点に対する受け入れ条件を整理しておく必要があります。そして、その受け入れ条件はそのまま最もハイレベルなテストケースとして使えます。
そのため、E2Eテストのテストケースは、理論的にはチケットを作り終えたタイミングで準備できています。別の表現をすれば、単体テストなどの他のテストレベルのテストを書くよりもずっと早い段階でE2Eテストのテストケースを考え始められるということです。
利用するユーザーが目的を達成できることを保証する
すでに何度か述べましたが、E2Eテストはユーザーがシステムを利用するときと同じようにテストする、ただ1つのテストレベルです。機能そのものの仕様ではなく、システムがユーザーによってどのように使われるのかをテストします。
ユーザーマニュアルをイメージするのがわかりやすいでしょう。初めて使うプロダクトを前にしたとき、ユーザーはそれぞれの機能の仕様について書かれたドキュメントよりも、真っ先にチュートリアルを読むはずです。E2Eテストは、チュートリアルのようなテストケースをそのまま自動テストにしたいときに利用できる、唯一のテストレベルです。
ユースケースのリストになる
さらに、そのシステムが「どのように使われるか」をよく考えて、それらをテストコードにしていくことを続けていけば、最終的にE2Eテストスイートはユースケースのリストになります。
このリストには、次のような情報が具体的に含まれているはずです。
- ユーザーは誰か
- そのユーザーは何を達成したいのか
- そのためにどのような操作をするのか
- どのような環境で操作するのか
開発者の多くは、新しいチームやプロジェクトに参加したときに、まずテストコードを見ます。そうすることで、手を加えようとしている機能の仕様のイメージをつかみます。多くの場合は単体テストなどですが、E2Eテストも同じようにシステムとそのユースケースをつかむ重要な情報になるでしょう。
プロジェクトに参加するメンバーが、ソフトウェアの使われ方について常に熟知しているとは限りません。ドキュメント化されていない特殊なユースケースでのバグも、場合によっては大きなクレームになり得ます。たとえば、大口の顧客だけに提供している特殊な機能などです。
E2Eテストコードを読む中でそうした事情について知ったり、あるいはそれ自体をテストできたりすることは、開発者がソフトウェアの使われ方を熟知するためのよい手段になります。