フロントエンドの書くべきだったテスト、書かなくてよかったテスト
by
Takepepe
Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
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
ご清聴ありがとうございました