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