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

ご清聴ありがとうございました