https://offers.connpass.com/event/299909/ 登壇資料
Takepepe #Offers_フロントエンドテストフロントエンドの書くべきだったテスト書かなくてよかったテスト
View Slide
自己紹介■ Takepepe(吉井 健文)■ フロントエンドエンジニア■ 社内横断開発組織に所属■ フロントエンド開発の横断サポート
フロントエンド開発のためのテスト入門■ 2023.4/24 翔泳社より刊行■ フロントエンド開発におけるテスト手法を紹介■ 単体テストからE2Eテストまでを体系的に■ 自動テストがはじめてという方にも
Agenda■ 【1】フロントエンドテストの目的■ 【2】書くべきだったテスト■ 【3】書かなくてよかったテスト実体験をまじえ、フロントエンドテストの考察をしていきます
【1】フロントエンドテストの目的
【1】フロントエンドテストの目的フロントエンドのテスト、書いていますか?よく相談される疑問点■ どの程度書けば良いか?■ どのように書けば良いか?■ どういった観点で書けばよいか?
【1】フロントエンドテストの目的フロントエンドのテスト、書いていますか?■ 自信をもって書けている方■ 自信はないが書けている方■ これから着手しようとしている方
【1】フロントエンドテストの目的フロントエンドのテスト、書いていますか?そもそも、なぜ「フロントエンド」のテストが必要か議論ができていますか?■ 自信をもって書けている方■ 自信はないが書けている方■ これから着手しようとしている方
【1】フロントエンドテストの目的テストを書き始めたころ■ テストの書き方がわかった!■ テストの概要が理解できた!■ テストを書くのは楽しい!!
【1】フロントエンドテストの目的テストが充実してきたころ■ 書きすぎているのでは?■ 効果があまりないのでは?■ 私たちにとって適切??
【1】フロントエンドテストの目的テストが充実してきたころ「作るもの」と同様「自動テスト」にも、それぞれ解が異なる■ 書きすぎているのでは?■ 効果があまりないのでは?■ 私たちにとって適切??
【1】フロントエンドテストの目的テストに期待すること■ バグを未然に防ぎたい■ インシデントを未然に防ぎたい■ 品質を向上したい
【1】フロントエンドテストの目的テストに期待すること■ バグを未然に防ぎたい(どんなバグが起こりそうですか?)■ インシデントを未然に防ぎたい(どんなインシデントですか?)■ 品質を向上したい(品質の高いコードはどのようなものですか?)
【1】フロントエンドテストの目的テストに求められる「解像度」■ このようにバグを防げる■ このようなインシデントを防ぎたい■ このような品質のコードを書きたい
【1】フロントエンドテストの目的テストに求められる「解像度」■ このようにバグを防げる■ このようなインシデントを防ぎたい■ このような品質のコードを書きたい根拠のある自動テストは、自信と安心につながる
【1】フロントエンドテストの目的テストに求められる「解像度」■ このようにバグを防げる■ このようなインシデントを防ぎたい■ このような品質のコードを書きたい具体例をあげ、目的の「解像度」を上げていきましょう
【2】書くべきだったテスト
【2】書くべきだったテストRouter に関連するテスト観点 1■ SPA フレームワーク や Web App フレームワーク(ex: Next.js)■ Routing に関する処理はフロントエンドの範囲■ コンポーネントの遷移先■ Router に関する処理
【2】書くべきだったテストRouter に関連するテスト観点 1■ SPA フレームワーク や Web App フレームワーク(ex: Next.js)■ Routing に関する処理はフロントエンドの範囲■ コンポーネントの遷移先■ Router に関する処理テストを厚めに書きたいポイント
【2】書くべきだったテストRouter に関連するテスト観点 1■ searchParams の参照・ query.foo の型推論は string | string[] | undefined である・ 通常 UI 操作では ?foo=bar(string)にしかならない前提
【2】書くべきだったテストRouter に関連するテスト観点 1■ searchParams の参照・ query.foo の型推論は string | string[] | undefined である・ 通常 UI 操作では ?foo=bar(string)にしかならない前提・ URL バーには ?foo=bar&foo=baz が入力できてしまう ・query.foo の期待値は “bar”(string) ・query.foo は実際は [“bar”, “baz”] (string[])をとりうる
【2】書くべきだったテストRouter に関連するテスト観点 1as string で握りつぶしていませんか?■ searchParams の参照・ query.foo の型推論は string | string[] | undefined である・ 通常 UI 操作では ?foo=bar(string)にしかならない前提・ URL バーには ?foo=bar&foo=baz が入力できてしまう ・query.foo の期待値は “bar”(string) ・query.foo は実際は [“bar”, “baz”] (string[])をとりうる
【2】書くべきだったテストRouter に関連するテスト観点 1テストを書いていると、稀なケースに注意が向く■ string[] のリクエストを、どのように処理すべき?❌ as string で握りつぶしているのでランタイムエラーに…
【2】書くべきだったテストRouter に関連するテスト観点 1どれを選んでも正解。どうあって欲しいかをテストコードで表明■ string[] のリクエストを、どのように処理すべき?❌ as string で握りつぶしているのでランタイムエラーに… (案A) 配列先頭の値を参照すること (案B) 不正なリクエストとしてエラー画面を表示すること (案C) 不正なリクエストなので何も処理を行わないこと
【2】書くべきだったテストRouter に関連するテスト観点 1A, B ,C どれが正しいか?要件定義に明記されていない -> 確認 -> テストを書く■ string[] のリクエストを、どのように処理すべき?❌ as string で握りつぶしているのでランタイムエラーに… (案A) 配列先頭の値を参照すること✅ (案B) 不正なリクエストとしてエラー画面を表示すること (案C) 不正なリクエストなので何も処理を行わないこと
【2】書くべきだったテストRouter に関連するテスト観点 1要件定義と実装の精度があがる■ string[] のリクエストを、どのように処理すべき?❌ as string で握りつぶしているのでランタイムエラーに… (案A) 配列先頭の値を参照すること✅ (案B) 不正なリクエストとしてエラー画面を表示すること (案C) 不正なリクエストなので何も処理を行わないこと
【2】書くべきだったテストRouter に関連するテスト観点 2searchParams をどう扱うかは、フロントエンドの責務■ /?foo=barという Route の画面における仕様✅「操作 A」があった場合 ?foo=bar は維持して /?foo=bar&baz=A に遷移✅「操作 B」があった場合 ?foo=bar は除外して /?baz=B に遷移
【2】書くべきだったテストRouter に関連するテスト観点 2「操作C」を追加した際に「操作 A」にリグレッションが発生■ /?foo=barという Route の画面における仕様❌「操作 A」があった場合 ?foo=bar は除外して /?baz=A に遷移✅「操作 B」があった場合 ?foo=bar は除外して /?baz=B に遷移✅「操作 C」があった場合 ?foo=bar は除外して /?baz=C に遷移
【2】書くべきだったテストRouter に関連するテスト観点 2「操作A」の自動テストで、リグレッションに気づけた■ /?foo=barという Route の画面における仕様❌「操作 A」があった場合 ?foo=bar は除外して /?baz=A に遷移✅「操作 B」があった場合 ?foo=bar は除外して /?baz=B に遷移✅「操作 C」があった場合 ?foo=bar は除外して /?baz=C に遷移
【2】書くべきだったテスト要件が複雑な機能のテスト観点■ 要件項目が多数あり複雑な機能✅「関数 A」が期待通りに動く
【2】書くべきだったテスト要件が複雑な機能のテスト観点■ 要件項目が多数あり複雑な機能✅「関数 A」が期待通りに動く✅「関数 B」は「関数 A」を使用、期待通りに動く✅「関数 C」は「関数 A」を使用、期待通りに動く
【2】書くべきだったテスト要件が複雑な機能のテスト観点■ 要件項目が多数あり複雑な機能✅「関数 A」が期待通りに動く✅「関数 B」は「関数 A」を使用、期待通りに動く✅「関数 C」は「関数 A」を使用、期待通りに動く✅「コンポーネント D」は「関数 B」を使用、期待通りに動く実装が完了し、リリースを待つ
【2】書くべきだったテスト要件が複雑な機能のテスト観点■ 要件項目が多数あり複雑な機能✅「関数 A」の修正が必要✅「関数 B」は「関数 A」を使用、修正が必要✅「関数 C」は「関数 A」を使用、期待通りに動く✅「コンポーネント D」は「関数 B」を使用、修正が必要コンポーネント D の仕様変更が発生。関数 B のために、関数 A に修正を加える
【2】書くべきだったテスト要件が複雑な機能のテスト観点■ 要件項目が多数あり複雑な機能✅「関数 A」が期待通りに動く✅「関数 B」は「関数 A」を使用、期待通りに動く❌「関数 C」は「関数 A」を使用、期待通りに動かない✅「コンポーネント D」は「関数 B」を使用、期待通りに動くコンポーネント D の仕様変更には対応できたが、関数 C がリグレッション
【2】書くべきだったテスト要件が複雑な機能のテスト観点■ 要件項目が多数あり複雑な機能✅「関数 A」が期待通りに動く✅「関数 B」は「関数 A」を使用、期待通りに動く✅「関数 C」は「関数 A」を使用、期待通りに動く✅「コンポーネント D」は「関数 B」を使用、期待通りに動くテストを書きながら開発すると「速度があがる」理由
【2】書くべきだったテスト要件が複雑な機能のテスト観点■ 要件項目が多数あり複雑な機能✅「関数 A」が期待通りに動く✅「関数 B」は「関数 A」を使用、期待通りに動く✅「関数 C」は「関数 A」を使用、期待通りに動く✅「コンポーネント D」は「関数 B」を使用、期待通りに動く後任開発者は、安心して機能追加や変更ができる
【2】書くべきだったテストコンポーネントの肥大化が防止できた■ 「コンポーネントテストが書きづらくて…」という相談 どこに何が書かれているか一目で理解できない 機能追加までのリードタイムが長い
【2】書くべきだったテストコンポーネントの肥大化が防止できた■ 「コンポーネントテストが書きづらくて…」という相談 コンポーネントを分割 ロジックを抽出✅ ロジックに対して単体テストを書く✅ コンポーネントのテストは薄めにリリース前にモジュール分割。適所に自動テストを追加
【2】書くべきだったテストコンポーネントの肥大化が防止できた「テストが書きづらい」という気づきが、よい設計のきっかけに■ 「コンポーネントテストが書きづらくて…」という相談 コンポーネントを分割 ロジックを抽出✅ ロジックに対して単体テストを書く✅ コンポーネントのテストは薄めに
【2】書くべきだったテストセマンティクスの不備に気づけた■ 「getByRole で要素を特定できなくて…」という相談・ label 要素を使用すべき箇所を見落とした・ `heading` role で取得できない、p 要素の見出し・ `link` role で取得できない、a 要素
【2】書くべきだったテストセマンティクスの不備に気づけた普段からテストを書く事で気づける■ 「getByRole で要素を特定できなくて…」という相談・ label 要素を使用すべき箇所を見落とした・ `heading` role で取得できない、p 要素の見出し・ `link` role で取得できない、a 要素
【2】書くべきだったテストキーボード操作の不備に気づけた■ 「キーボードで操作できなくて…」という相談・ フォーカスの当たらない、div 要素で作られたボタン・ スペースキーを押下しても開かない、ドロップダウンメニュー
【2】書くべきだったテストキーボード操作の不備に気づけた要件定義になかったので、実装観点になかった■ 「キーボードで操作できなくて…」という相談・ フォーカスの当たらない、div 要素で作られたボタン・ スペースキーを押下しても開かない、ドロップダウンメニュー
【2】書くべきだったテスト共通 UI コンポーネントのテスト観点■ デザインシステム整備前の手動テスト (目視による)✅「画面 A」がデザイン通りに実装されている✅「画面 B」がデザイン通りに実装されている✅「画面 C」がデザイン通りに実装されているデザインシステムの適用で、画面が崩れないか?
【2】書くべきだったテスト共通 UI コンポーネントのテスト観点■ デザインシステム整備が完了、各画面で使用するように✅「画面 A」がデザイン通りに実装されている?✅「画面 B」がデザイン通りに実装されている?✅「画面 C」がデザイン通りに実装されている?見た目に関するリグレッションは VRT を活用
【2】書くべきだったテスト共通 UI コンポーネントのテスト観点■ デザインシステム整備が完了、各画面で使用するように✅「画面 A」がデザイン通りに実装されている✅「画面 B」がデザイン通りに実装されている✅「画面 C」がデザイン通りに実装されているリファクタリングが積極的に行えた
第3章書かなくてよかったテスト
【3】書かなくてよかったテスト不適切なテストサイズ■ 「ブラウザの自動テストが Flaky で…」という相談・ ブラウザの自動テストは Flaky になりがち・ 他のテスト手法で担保できるテスト観点・ 各テスト手法の、得手不得手を把握しておく必要がある
【3】書かなくてよかったテスト不適切なテストサイズ■ 「ブラウザの自動テストが Flaky で…」という相談・ ブラウザの自動テストは Flaky になりがち・ 他のテスト手法で担保できるテスト観点・ 各テスト手法の、得手不得手を把握しておく必要がある安定性 X 忠実性 = 信頼できるテスト
【3】書かなくてよかったテスト書かれることが目的になってしまったテスト■ テストをたくさん書いたけれど…・ テスト観点がない・ モックばかりで意義を感じられない・ 他のテストコードと同程度の量を何となく書いた
【3】書かなくてよかったテスト書かれることが目的になってしまったテスト■ テストをたくさん書いたけれど…・ テスト観点がない・ モックばかりで意義を感じられない・ 他のテストコードと同程度の量を何となく書いた目的がないのであれば「書かなくてもよい」
【3】書かなくてよかったテスト無理に aria 属性を付け足したテスト■ 「getByRole で取得できなかったのでつい…」・ テストのための符号として aria 属性を付与している・ role="foo-button"と書かれているボタン(体験談)
【3】書かなくてよかったテスト無理に aria 属性を付け足したテスト■ 「getByRole で取得できなかったのでつい…」・ テストのための符号として aria 属性を付与している・ role="foo-button"と書かれているボタン(体験談)No ARIA is better than Bad ARIA
【3】書かなくてよかったテスト無理に aria 属性を付け足したテスト■ 「getByRole で取得できなかったのでつい…」・ テストのための符号として aria 属性を付与している・ role="foo-button"と書かれているボタン(体験談)正しくない aria 属性を付与するよりも、付与されない方がまだマシ
【3】書かなくてよかったテスト過剰な VRT■ 「CI が遅くて…」という相談・ 全コンポーネントの Storybook を対象に VRT を実施・ CI が通るまでに数十分かかる・ ビジュアルリグレッションが発生するケースを想定できているか?
【3】書かなくてよかったテスト過剰な VRT■ 「CI が遅くて…」という相談・ 全コンポーネントの Storybook を対象に VRT を実施・ CI が通るまでに数十分かかる・ ビジュアルリグレッションが発生するケースを想定できているか?他のテストタイプでカバーできているならば、数は減らせる
まとめ
まとめ「こういう理由で必要」と言い切れるのは、良いテストだと思います■ 「書くべきだった」テスト・ 仕様とコードの解像度をあげてくれるテスト・ 安心してリファクタできるテスト・ 自信を与えてくれるテスト
まとめテストの要不要を議論したり、目的を見直す機会に■ 「書かなくてよかった」テスト・ 書く事が目的になってしまったテスト・ よくない方向に誘導してしまったテスト・ 考慮なく作業的に追加されたテスト
ご清聴ありがとうございました