SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

翔泳社の本(AD)

フロントエンドのテスト手法、何がある? 知っておきたいテストとその戦略を網羅的に解説

  • このエントリーをはてなブックマークに追加

 フロントエンドの役割が増す中、効率のよく確実なテストを行うことはもはや常識になってきています。フロントエンド・アーキテクトとして活躍する吉井健文さんの著書『フロントエンド開発のためのテスト入門』(翔泳社)では、フロントエンドエンジニアが押さておくべきテストの基本と具体的な実践手法が解説されています。今回は本書から、テストの種類とその戦略について網羅的に説明されている「第2章 テスト手法とテスト戦略」を抜粋して紹介します。

  • このエントリーをはてなブックマークに追加

 本記事は『フロントエンド開発のためのテスト入門 今からでも知っておきたい自動テスト戦略の必須知識』の「第2章 テスト手法とテスト戦略」から抜粋したものです。掲載にあたって一部を編集しています

 なお、5月10日(水)に、著者の吉井健文さんをゲストに迎えたCodeZine主催のウェビナー「Webフロントエンドのための実践『テスト』手法」が開催されます。こちらもぜひチェックしてください!

ウェビナーの詳細を見る

範囲と目的で考えるテスト

 本書では多くのテスト手法を取り上げているため、何から着手すればよいのかわからないと感じるかもしれません。詳細なテスト手法の章に進む前に、まずは本章で「フロントエンドテストの範囲と目的」について理解を深めることをおすすめします。

 闇雲に取り組むよりも「範囲」と「目的」の組み合わせを理解し、適切な自動テストをコミットし、確かなメリットを実感していきましょう。

 本章は、本書を読み終えた方にとって、まとめの章としても活用できます。一通りの概要を掴んだ後にこの章を読み返すことで、フロントエンドテストの全容について、より理解が深まるでしょう。

テストの範囲

 Webアプリケーションコードは、様々なモジュールを組み合わせて実装します。例えば1つの機能を提供するためには、次のような一連のモジュール(システム)が必要です。

  • ①ライブラリが提供する関数
  • ②ロジックを担う関数
  • ③UIを表現する関数
  • ④Web APIクライアント
  • ⑤APIサーバー
  • ⑥DBサーバー

 フロントエンドの自動テストを書くとき、この①〜⑥のうち「どこからどこまでの範囲をカバーしたテストであるか」を意識する必要があります。Webフロントエンド開発におけるテストの範囲(テストレベル)は、概ね次の4つに分類されます。

静的解析

 TypeScriptやESLintによる静的解析です。一つ一つのモジュール内部検証だけでなく、②と③の間、③と④の間、というように「隣接するモジュール間連携の不整合」に対して検証します。

単体テスト

 ②のみ、③のみ、というように「モジュール単体が提供する機能」に着目したテストです。独立した検証が行えるため、アプリケーション稼働時にはめったに発生しないケース(コーナーケース)の検証に向いています。

結合テスト

 ①〜④まで、②〜③までというように「モジュールをつなげることで提供できる機能」に着目したテストです。範囲が広いほどテスト対象を効率よくカバーすることができますが、相対的にざっくりとした検証になる傾向があります。

E2Eテスト

 ①〜⑥を通し、ヘッドレスブラウザ+UIオートメーションで実施するテストです。最も広範囲な結合テストともいえるもので、アプリケーション稼働状況に忠実なテストです。

テストの目的

 テストは目的によって「テストタイプ」に分類されます。ソフトウェアテストで有名なテストタイプが「機能テスト、非機能テスト、ホワイトボックステスト、リグレッションテスト」です。

 テストタイプは検証目的に応じて設定され、テストタイプごとに適したテストツールが存在します。ツール単体で実現するものもあれば、組み合わせることで実現するものもあります。Webフロントエンドテストにおいて代表的なテストタイプとしては、次のものが挙がります。

機能テスト(インタラクションテスト)

 開発対象の機能に不具合がないかを検証するのが「機能テスト」です。Webフロントエンドにおける開発対象機能の大部分は、UIコンポーネントの操作(インタラクション)が起点となります。そのため、インタラクションテストが機能テストそのものになるケースが多く、重要視されます。本物のブラウザAPIを使用することが重要なテストの場合、ヘッドレスブラウザ+UIオートメーションを使用して自動テストを書きます。

非機能テスト(アクセシビリティテスト)

 非機能テストのうち、心身特性に隔てのない製品を提供できているかという検証が「アクセシビリティテスト」です。近年、Webアクセシビリティに関するAPIが様々なプラットフォームで展開されており、自動テストでも客観的に判定できる環境が整っています。

リグレッションテスト

 特定時点から、前後の差分を検出して想定外の不具合が発生していないかを検証するテストが「リグレッションテスト」です。Webフロントエンドにおける開発対象の大部分が見た目(ビジュアル)を持つUIコンポーネントであることから、「ビジュアルリグレッションテスト」が重要視されます。

フロントエンドテストの範囲

 Webフロントエンドテストの範囲について、さらに詳しく解説します。

静的解析

 TypeScriptによる静的解析は、バグの早期発見に欠かせない存在です。ランタイムの挙動を再現する型の絞り込みは特に優れています。例えば、if文による分岐で値を安全に扱えるようになります(リスト1)。

リスト1 ランタイム挙動を再現する型推論
function getMessage(name: string | undefined) {
  const a = name; // a: string | undefined
  if (!name) {
    return `Hello anonymous!`;
  }
  // if文の分岐とreturnによりundefinedではないと判定される
  const b = name; // b: string
  return `Hello ${name}!`;
}

 また、関数の戻り値が期待通りになっているかを検証するのに役立ちます。リスト2では、戻り値の型がstring | undefined(文字列またはundefined)とならないよう、関数のブロック末尾で例外をスローしています。この処理により文字列を必ず返すことになるため、返り値は必ずstring型である、という型推論になります。

リスト2 戻り値型推論はstring | undefinedのため一致せず、型エラーとなる
function checkType(type: "A" | "B" | "C"): string {
  const message: string = "valid type";
  if (type === "A") {
    return message;
  }
  if (type === "B") {
    return message;
    }
  // 例外発生有無によって、関数の戻り値型推論が変わる
  // throw new Error('invalid type')
}

 コーディングガイドラインのためのESLintも、静的解析のうちの1つです。不適切な構文を回避することで、潜在的なバグの混入を未然に防ぐ効果があります(リスト3)。ライブラリ開発者が提供する、当該ライブラリ向けの推奨設定も導入すべきでしょう。正しい使用方法が促されるため、将来的に非推奨になるAPIに気づける、といったメリットがあります。

リスト3 ライブラリが推奨するコーディングガイドライン違反
useEffect(() => {
  console.log(name);
}, []);    // 依存している参照値nameを配列に含むべき、というLintエラーが発生

単体テスト

 単体テストは最も基本的なテストです。テスト対象モジュールが、定められた入力値から期待する出力値が得られるかをテストします。SPA開発において、UIコンポーネントはテストしやすい対象です。入力値(Props)から出力値(HTMLのブロック)を得るUIコンポーネントは、関数の単体テストと同じ要領でテストができます。

 モジュールによっては、めったに発生しないケース(コーナーケース)に限り、処理を中断したほうがよいと判断されることがあります。このとき「どういった条件」で例外をスローするべきかという検討に、単体テストは役立ちます。「このような条件になり得ないか?」「なり得るならどう処理すべきか?」といった検討を重ねることにより、コードの考慮漏れに気づくきっかけとなります(図1)。

図1 単体テストで関数の考慮漏れがないか検討する
図1 単体テストで関数の考慮漏れがないか検討する

結合テスト

 結合テストは、複数モジュールが連動する機能に着目したテストです。大きなUIコンポーネントは単体で機能提供することはほとんどなく、複数モジュールを組み合わせることにより機能します。この機能は主に、インタラクションを通して提供されます。Webアプリケーションの要素一覧画面について考えてみましょう。

  • ①セレクトボックスを操作する
  • ②URLの検索クエリーが変化する
  • ③検索クエリーの変化により、データ取得APIが呼ばれる
  • ④一覧表示内容が更新される

 このように「セレクトボックスを操作する」というたった1つのインタラクションで、最終的に「一覧表示内容が更新される」という処理までが実施されます。「①を実行したら④が実行されること」というテストが、この機能に着目した結合テストです。

 ①〜④の例は範囲の広い結合テストですが、①〜②までのように、範囲の狭い結合テストも効果があります。コーナーケースの組み合わせで複雑になっている場合、狭い範囲で結合テストを行ったほうが、何に対してテストを実施しているのかがより明確になるためです。

E2Eテスト

 UIテストに加え、外部ストレージや連携するサブシステムを含むテストを、本書では「E2Eテスト」(End to Endテスト)と称します。入力内容に応じて保存された値が更新されるので、画面をまたいだ機能はもちろん、外部連携が正常に機能しているかを検証できます。

フロントエンドテストの目的

 Webフロントエンドテストの目的について、さらに詳しく解説します。

機能テスト(インタラクションテスト)

 Webフロントエンドの主な開発対象は、ユーザーが操作するUIコンポーネントです。操作を与えることによって状態が変化し、ユーザーが求める情報を提供、更新します。そのため、インタラクションテストが機能テストそのものになるケースが多く、本書で紹介するテストコードはインタラクションテストが大半を占めます。

 インタラクションテストと聞くと、実際のブラウザ(Chromiumなど)をヘッドレスモードで起動し、UIオートメーションで実施するテストが思い浮かびます。しかし、Reactなどのライブラリで実装されたUIコンポーネントにおいては、ブラウザなしでもインタラクションテストができる環境が整っています。詳細は後の章で解説しますが、これは「仮想ブラウザ環境」でテストを実行しているためです。

実際のブラウザなしでも可能なインタラクションテスト具体例

  • ボタンを押下すると、コールバック関数が呼ばれる
  • 文字を入力すると、送信ボタンが活性化する
  • ログアウトボタンを押下すると、ログイン画面に遷移する
図2 実際のブラウザなしでも可能なインタラクションテストの具体例
図2 実際のブラウザなしでも可能なインタラクションテストの具体例

 実際のブラウザがなければ成立しない機能テストは、ヘッドレスブラウザ+UIオートメーションを使用します。これは、スクロールやセッションストレージなどの機能が、仮想ブラウザ環境において不十分なためです。本番と同等の環境、つまりブラウザ環境を忠実に再現する必要のある機能テストは、こちらを選択します。

実際のブラウザが必要なインタラクションテスト具体例

  • 最下部までスクロールすると、新しいデータがロードされる
  • セッションストレージに保存した値が復元される
図3 実際のブラウザが必要なインタラクションテスト具体例
図3 実際のブラウザが必要なインタラクションテスト具体例

非機能テスト(アクセシビリティテスト)

 アクセシビリティテストは、非機能テストのうちの1つです。アクセシビリティテストと一口にいっても、検証項目は多岐にわたります。「キーボード入力による操作が充実しているか」「視認性に問題のないコントラスト比となっているか」という検証項目では、それぞれ適したツールが異なります。

 とはいえ、本書で解説するアクセシビリティテストは機能テストで使用するツールと同じ「仮想ブラウザ環境/実際のブラウザ環境」を利用します。機能テスト+αという感覚で取り組めるため、アクセシビリティ品質向上のきっかけに適しています。

アクセシビリティテスト具体例

  • チェックボックスとして、チェックできる
  • エラーレスポンスが表示された場合、エラー文言が読み上げ対象としてレンダリングされる
  • 表示している画面で、アクセシビリティ違反がないか調べる
図4 アクセシビリティテスト
図4 アクセシビリティテスト

ビジュアルリグレッションテスト  CSSはUIコンポーネントに定義されたスタイルだけでなく、ブラウザに読み込まれたCSS全てから影響を受けます。ヘッドレスブラウザに描画された内容をキャプチャし、キャプチャ画像を比較することで見た目のリグレッションが発生していないかを検証します。表示されたUIコンポーネントの画像比較にとどまらず、ユーザー操作を与えたことにより変化したUIコンポーネントの画像比較も可能です。

ビジュアルリグレッションテスト具体例

  • ボタンの見た目に、リグレッションがない
  • メニューバーを開いた状態に、リグレッションがない
  • 表示された画面に、リグレッションがない
図5 ビジュアルリグレッションテスト
図5 ビジュアルリグレッションテスト

テスト戦略モデル

 これまでに説明してきたテストの種類は、図6のようにいくつかの層に分類して考えることができます。上層のテストは忠実性の高いテスト(本物に近いテスト)になることが期待できます。初見では、忠実性の高いテストをたくさん揃えたほうが、よりよいテスト戦略となるように思えます。しかし、上層のテストほどメンテナンスの工数が必要とされ、実行時間がかかります。

 上層のテストは、実行するにあたり本物に近いテスト環境を揃えます。具体的には、テスト用に用意したDBサーバーを起動してセットアップするといった工程が必要になります。ほかにも、テストを実行する度に、連携する外部システムのレスポンスを全て待つ必要があります。

 このテストに必要な「コスト」は開発に与える影響が大きく、開発者間で十分に検討する必要があります。総じて「コスト配分」をどのように設計して最適化を行うかが、テスト戦略最大の検討事項となります。この検討事項に対してどう取り組むべきかについて、先人が提案したテスト戦略モデルを紹介します。

図6 テストの範囲とコストの相関関係
図6 テストの範囲とコストの相関関係

アイスクリームコーン型、テストピラミッド型

 上層のテストが多く書かれた「アイスクリームコーン」は、戦略モデルのアンチパターンとして参照されるモデル図です。運用コストが高いだけでなく、稀に失敗する不安定なテストがより多くのコストを必要とします。

 もし全てのテストがパスするまでに何十分もかかってしまうと、日常的な開発フローに影響が出ます。自動テストによって開発体験が著しく低下するという、本末転倒の悪影響が生じてしまいます。この対策として実行頻度を落としてしまっては、自動テストの信頼性が疑わしくなります。

 「テストピラミッド」とは、Mike Cohn氏による2009年の著書『Succeeding with Agile』で紹介されたテスト戦略モデル図です。この戦略モデルでは「テストレベルごとにどれほどテストを書くべきか?」という観点が示されています。下層のテストを多く書くことで、より安定した費用対効果の高いテスト戦略になる、ということが提唱されています。

 ブラウザを含む上層のテストは、実行時間に関するコストが高いです。そのため、下層のテストを充実させることで、安定かつ高速なテスト戦略とすることができます。テストピラミッドが優れているという観点は、フロントエンドの自動テストにおいても同じであるといえます(図7)。

図7 アイスクリームコーン型(図左)、テストピラミッド型(図右)の比較
図7 アイスクリームコーン型(図左)、テストピラミッド型(図右)の比較

テスティングトロフィー型

 「テスティングトロフィー」とは、本書でメインに取り上げる「Testing Library」の開発者、Kent C. Dodds.氏が提唱するテスト戦略モデル図です。最も比重を置くべきなのは「結合テスト」であるという主旨のものです。

 Webフロントエンド開発において、単体のUIコンポーネントだけで成立する機能はほとんどありません。例えば、UIを操作することにより外部Web APIへのリクエストが発生する機能です。このような機能は一般的に、複数のモジュールを組み合わせることにより実現します。

 フロントエンドが提供する機能は、ユーザー操作(インタラクション)を起点に提供されます。そのため、ユーザー操作を起点とした結合テストを充実させることこそが、よりよいテスト戦略になるという意図が込められています(図8)。

図8 テスティングトロフィー型
図8 テスティングトロフィー型

 Testing LibraryとJestを使用したテストは、ヘッドレスブラウザを用意しなくともユーザー操作が行えるという特徴があります。つまり、実行速度が速く、忠実性の高いテストが叶う可能性が高いことを意味します。

テスト戦略計画

 テスト戦略モデル図を参考に、プロジェクトに最適なテスト手法を選択していきます。自分達のプロジェクトに向き合い「テスト対象は何なのか?」「目的は何なのか?」という判断基準を持つことが大切です。プロジェクトのテスト戦略を決定するにあたり、判断基準の一例を紹介します。

テストがなく、リファクタリングに不安がある場合

 リリース済みのプロジェクトにテストがない場合、リファクタリングの取り組みに不安が伴います。まず、リリース済みの機能にどういったものがあるかをリストアップしましょう。「変更前後で欠陥が混入していないこと」というテストは、リグレッションテストに相当します。リグレッションテストを書くことによって、ソースコードの積極的なリファクタリングに取り組めます。

 Web APIサーバーへの依存が綺麗に分割されていない場合、テストが書きづらく手詰まりになることがあります。このときおすすめなのが、モックサーバーを使用した結合テストです。ソースコードを修正せずにテストが書けるようになる場合があるため「リファクタリングする前にテストを書く」という需要に応えます。リリース済みのプロジェクトの場合は特に効果的でしょう。

 段階的に結合テストを増やすことで、リファクタリングに取り組める箇所が増えてきます。リファクタリングやテスト拡充という工程で、より安定したピラミッド型を目指していくとよいでしょう(図9)。

図9 モックサーバーに差し替え、段階的にテストを書いていく
図9 モックサーバーに差し替え、段階的にテストを書いていく

 モックサーバーを使用した具体的な結合テスト手法については、第7章で紹介します。

レスポンシブレイアウトを含むプロジェクトの場合

 レスポンシブレイアウトは、1つのHTMLドキュメントで複数の見た目を提供します。JavaScriptやユーザーエージェントによる表示分岐だけではなく、CSS定義による表示分岐処理が多く含まれます。レスポンシブレイアウトの場合、PC向けにスタイル修正を行ったつもりが、SP(スマートフォン)側にも影響してしまった、ということが起こり得ます(図10)。

図10 考慮漏れで発生する、想定外のデザイン崩れ
図10 考慮漏れで発生する、想定外のデザイン崩れ

 Testing Libraryを使用したテストでは、スタイルを含むテストを十分に書くことができません。そのため、デバイス間で異なるスタイルが提供される場合、CSS定義を解釈して表示結果を検証するブラウザテストが必要です。こういったシーンにおいて、ブラウザを用いたビジュアルリグレッションテストが支えになります。

 Storybookが導入されている場合、UIコンポーネント単位でビジュアルリグレッションテストが可能になります。レスポンシブレイアウトを含むプロジェクトの場合、Storybookを活用したテスト手法を中心に据え、不足しているテストを拡充していくとよいでしょう。

 Storybookを使用したUIコンポーネントのテストは第8章で、reg-suitを使用したビジュアルリグレッションテストは第9章で紹介します。

データ永続層を含めたE2Eテストを行いたい場合

 モックサーバーではなく実際のWeb APIサーバーを含めたE2Eテストを行いたい場合、テスト用のステージング環境を使用します。ステージング環境とは、より本番環境に近い構成をテスト用に用意した環境のことを指します。E2Eテストはプロジェクトリリース前に、テスト計画書にもとづくテストエンジニアの手動テストが実施されることが多いですが、ブラウザを用いたUIオートメーションで行うこともあります。

 また、ステージング環境を準備しない自動テストの方法があります。関連システムを再現するテストコンテナーを用意し、CI(継続的インテグレーション)で起動、テスト実行することにより、複数システムの連携をテストする手法です。テスト環境構築コストが比較的少なく、開発担当のエンジニア単独で用意することもできます(図11)。

図11 ステージング環境とテストコンテナーの違い
図11 ステージング環境とテストコンテナーの違い

 E2Eテスティングフレームワークの使い方だけでなく、コンテナー型仮想化環境の知識、関連システムの初期セットアップ知識が必要です。永続層を含めたE2Eテスト手法については、第10章で紹介します。

テストを書きすぎていないかの見直しを

 これまで紹介したように、テストタイプやテスト戦略は多種多様です。複数のテストを書いているうちに、守備範囲が重複していることに気づくかもしれません。前述のようにStorybookやE2Eテストが重要と判断した場合、UIコンポーネントテストはエラーパターンのみで十分だという意見も出るでしょう。

 このような重複は、テストを拡充するときに気づきます。「これまで書いていたテストは、このくらい書いていたので」という指標は、チームで共有しやすい共通認識です。しかし、プロジェクト全体で書かれているテストを俯瞰してみたときに「少し書きすぎでは?」と感じるのはよくあることです。

 過剰に書いたテストは、思い切って減らしていくことも大切です。本書のサンプルは、学習向けにかなり手厚くテストコードを書いていますが、どんなプロジェクトにも最適なものとは限りません。自分達のプロジェクトに向き合い、技術構成と照らし合わせながら「どういったテスト戦略が自分達の目的に合致するか」という視点を、常に持ち続けるとよいでしょう。

フロントエンド開発のためのテスト入門 今からでも知っておきたい自動テスト戦略の必須知識

Amazon  SEshop  その他

 
フロントエンド開発のためのテスト入門
今からでも知っておきたい自動テスト戦略の必須知識

著者:吉井健文
発売日:2023年4月24日(月)
定価:3,080円(本体2,800円+税10%)

本書について

本書は「フロントエンドにおけるテスト」をテーマに、基本的なテストコードの書き方や、目的に応じたテスト手法・ツールの使い分け方を解説します。フロントエンドならではの具体的なテスト課題に重点を置いており、基本から実践まで必要な知識を体系的に身につけられます。

この記事は参考になりましたか?

  • このエントリーをはてなブックマークに追加

【AD】本記事の内容は記事掲載開始時点のものです 企画・制作 株式会社翔泳社

この記事は参考になりましたか?

この記事をシェア

  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/17672 2023/05/02 07:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング