Upgrade to Pro — share decks privately, control downloads, hide ads and more …

フロントエンドの書くべきだったテスト、書かなくてよかったテスト

Takepepe
November 16, 2023

 フロントエンドの書くべきだったテスト、書かなくてよかったテスト

Takepepe

November 16, 2023
Tweet

More Decks by Takepepe

Other Decks in Programming

Transcript

  1. Takepepe #Offers_フロントエンドテスト
    フロントエンドの
    書くべきだったテスト
    書かなくてよかったテスト

    View full-size slide

  2. 自己紹介
    ■ Takepepe(吉井 健文)
    ■ フロントエンドエンジニア
    ■ 社内横断開発組織に所属
    ■ フロントエンド開発の横断サポート

    View full-size slide

  3. フロントエンド開発のためのテスト入門
    ■ 2023.4/24 翔泳社より刊行
    ■ フロントエンド開発におけるテスト手法を紹介
    ■ 単体テストからE2Eテストまでを体系的に
    ■ 自動テストがはじめてという方にも

    View full-size slide

  4. Agenda
    ■ 【1】フロントエンドテストの目的
    ■ 【2】書くべきだったテスト
    ■ 【3】書かなくてよかったテスト
    実体験をまじえ、フロントエンドテストの考察をしていきます

    View full-size slide

  5. 【1】フロントエンドテストの目的

    View full-size slide

  6. 【1】フロントエンドテストの目的
    フロントエンドのテスト、書いていますか?
    よく相談される疑問点
    ■ どの程度書けば良いか?
    ■ どのように書けば良いか?
    ■ どういった観点で書けばよいか?

    View full-size slide

  7. 【1】フロントエンドテストの目的
    フロントエンドのテスト、書いていますか?
    ■ 自信をもって書けている方
    ■ 自信はないが書けている方
    ■ これから着手しようとしている方

    View full-size slide

  8. 【1】フロントエンドテストの目的
    フロントエンドのテスト、書いていますか?
    そもそも、なぜ「フロントエンド」のテストが必要か議論ができていますか?
    ■ 自信をもって書けている方
    ■ 自信はないが書けている方
    ■ これから着手しようとしている方

    View full-size slide

  9. 【1】フロントエンドテストの目的
    テストを書き始めたころ
    ■ テストの書き方がわかった!
    ■ テストの概要が理解できた!
    ■ テストを書くのは楽しい!!

    View full-size slide

  10. 【1】フロントエンドテストの目的
    テストが充実してきたころ
    ■ 書きすぎているのでは?
    ■ 効果があまりないのでは?
    ■ 私たちにとって適切??

    View full-size slide

  11. 【1】フロントエンドテストの目的
    テストが充実してきたころ
    「作るもの」と同様「自動テスト」にも、それぞれ解が異なる
    ■ 書きすぎているのでは?
    ■ 効果があまりないのでは?
    ■ 私たちにとって適切??

    View full-size slide

  12. 【1】フロントエンドテストの目的
    テストに期待すること
    ■ バグを未然に防ぎたい
    ■ インシデントを未然に防ぎたい
    ■ 品質を向上したい

    View full-size slide

  13. 【1】フロントエンドテストの目的
    テストに期待すること
    ■ バグを未然に防ぎたい(どんなバグが起こりそうですか?)
    ■ インシデントを未然に防ぎたい(どんなインシデントですか?)
    ■ 品質を向上したい(品質の高いコードはどのようなものですか?)

    View full-size slide

  14. 【1】フロントエンドテストの目的
    テストに求められる「解像度」
    ■ このようにバグを防げる
    ■ このようなインシデントを防ぎたい
    ■ このような品質のコードを書きたい

    View full-size slide

  15. 【1】フロントエンドテストの目的
    テストに求められる「解像度」
    ■ このようにバグを防げる
    ■ このようなインシデントを防ぎたい
    ■ このような品質のコードを書きたい
    根拠のある自動テストは、自信と安心につながる

    View full-size slide

  16. 【1】フロントエンドテストの目的
    テストに求められる「解像度」
    ■ このようにバグを防げる
    ■ このようなインシデントを防ぎたい
    ■ このような品質のコードを書きたい
    具体例をあげ、目的の「解像度」を上げていきましょう

    View full-size slide

  17. 【2】書くべきだったテスト

    View full-size slide

  18. 【2】書くべきだったテスト
    Router に関連するテスト観点 1
    ■ SPA フレームワーク や Web App フレームワーク(ex: Next.js)
    ■ Routing に関する処理はフロントエンドの範囲
    ■  コンポーネントの遷移先
    ■ Router に関する処理

    View full-size slide

  19. 【2】書くべきだったテスト
    Router に関連するテスト観点 1
    ■ SPA フレームワーク や Web App フレームワーク(ex: Next.js)
    ■ Routing に関する処理はフロントエンドの範囲
    ■  コンポーネントの遷移先
    ■ Router に関する処理
    テストを厚めに書きたいポイント

    View full-size slide

  20. 【2】書くべきだったテスト
    Router に関連するテスト観点 1
    ■ searchParams の参照
    ・ query.foo の型推論は string | string[] | undefined である
    ・ 通常 UI 操作では ?foo=bar(string)にしかならない前提

    View full-size slide

  21. 【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[])をとりうる

    View full-size slide

  22. 【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[])をとりうる

    View full-size slide

  23. 【2】書くべきだったテスト
    Router に関連するテスト観点 1
    テストを書いていると、稀なケースに注意が向く
    ■ string[] のリクエストを、どのように処理すべき?
    ❌ as string で握りつぶしているのでランタイムエラーに

    View full-size slide

  24. 【2】書くべきだったテスト
    Router に関連するテスト観点 1
    どれを選んでも正解。どうあって欲しいかをテストコードで表明
    ■ string[] のリクエストを、どのように処理すべき?
    ❌ as string で握りつぶしているのでランタイムエラーに

      (案A) 配列先頭の値を参照すること
      (案B) 不正なリクエストとしてエラー画面を表示すること
      (案C) 不正なリクエストなので何も処理を行わないこと

    View full-size slide

  25. 【2】書くべきだったテスト
    Router に関連するテスト観点 1
    A, B ,C どれが正しいか?要件定義に明記されていない -> 確認 -> テストを書く
    ■ string[] のリクエストを、どのように処理すべき?
    ❌ as string で握りつぶしているのでランタイムエラーに

      (案A) 配列先頭の値を参照すること
    ✅ (案B) 不正なリクエストとしてエラー画面を表示すること
      (案C) 不正なリクエストなので何も処理を行わないこと

    View full-size slide

  26. 【2】書くべきだったテスト
    Router に関連するテスト観点 1
    要件定義と実装の精度があがる
    ■ string[] のリクエストを、どのように処理すべき?
    ❌ as string で握りつぶしているのでランタイムエラーに

      (案A) 配列先頭の値を参照すること
    ✅ (案B) 不正なリクエストとしてエラー画面を表示すること
      (案C) 不正なリクエストなので何も処理を行わないこと

    View full-size slide

  27. 【2】書くべきだったテスト
    Router に関連するテスト観点 2
    searchParams をどう扱うかは、フロントエンドの責務
    ■ /?foo=barという Route の画面における仕様
    ✅「操作 A」があった場合 ?foo=bar は維持して /?foo=bar&baz=A に遷移
    ✅「操作 B」があった場合 ?foo=bar は除外して /?baz=B に遷移

    View full-size slide

  28. 【2】書くべきだったテスト
    Router に関連するテスト観点 2
    「操作C」を追加した際に「操作 A」にリグレッションが発生
    ■ /?foo=barという Route の画面における仕様
    ❌「操作 A」があった場合 ?foo=bar は除外して /?baz=A に遷移
    ✅「操作 B」があった場合 ?foo=bar は除外して /?baz=B に遷移
    ✅「操作 C」があった場合 ?foo=bar は除外して /?baz=C に遷移

    View full-size slide

  29. 【2】書くべきだったテスト
    Router に関連するテスト観点 2
    「操作A」の自動テストで、リグレッションに気づけた
    ■ /?foo=barという Route の画面における仕様
    ❌「操作 A」があった場合 ?foo=bar は除外して /?baz=A に遷移
    ✅「操作 B」があった場合 ?foo=bar は除外して /?baz=B に遷移
    ✅「操作 C」があった場合 ?foo=bar は除外して /?baz=C に遷移

    View full-size slide

  30. 【2】書くべきだったテスト
    要件が複雑な機能のテスト観点
    ■ 要件項目が多数あり複雑な機能
    ✅「関数 A」が期待通りに動く

    View full-size slide

  31. 【2】書くべきだったテスト
    要件が複雑な機能のテスト観点
    ■ 要件項目が多数あり複雑な機能
    ✅「関数 A」が期待通りに動く
    ✅「関数 B」は「関数 A」を使用、期待通りに動く
    ✅「関数 C」は「関数 A」を使用、期待通りに動く

    View full-size slide

  32. 【2】書くべきだったテスト
    要件が複雑な機能のテスト観点
    ■ 要件項目が多数あり複雑な機能
    ✅「関数 A」が期待通りに動く
    ✅「関数 B」は「関数 A」を使用、期待通りに動く
    ✅「関数 C」は「関数 A」を使用、期待通りに動く
    ✅「コンポーネント D」は「関数 B」を使用、期待通りに動く
    実装が完了し、リリースを待つ

    View full-size slide

  33. 【2】書くべきだったテスト
    要件が複雑な機能のテスト観点
    ■ 要件項目が多数あり複雑な機能
    ✅「関数 A」の修正が必要
    ✅「関数 B」は「関数 A」を使用、修正が必要
    ✅「関数 C」は「関数 A」を使用、期待通りに動く
    ✅「コンポーネント D」は「関数 B」を使用、修正が必要
    コンポーネント D の仕様変更が発生。関数 B のために、関数 A に修正を加える

    View full-size slide

  34. 【2】書くべきだったテスト
    要件が複雑な機能のテスト観点
    ■ 要件項目が多数あり複雑な機能
    ✅「関数 A」が期待通りに動く
    ✅「関数 B」は「関数 A」を使用、期待通りに動く
    ❌「関数 C」は「関数 A」を使用、期待通りに動かない
    ✅「コンポーネント D」は「関数 B」を使用、期待通りに動く
    コンポーネント D の仕様変更には対応できたが、関数 C がリグレッション

    View full-size slide

  35. 【2】書くべきだったテスト
    要件が複雑な機能のテスト観点
    ■ 要件項目が多数あり複雑な機能
    ✅「関数 A」が期待通りに動く
    ✅「関数 B」は「関数 A」を使用、期待通りに動く
    ✅「関数 C」は「関数 A」を使用、期待通りに動く
    ✅「コンポーネント D」は「関数 B」を使用、期待通りに動く
    テストを書きながら開発すると「速度があがる」理由

    View full-size slide

  36. 【2】書くべきだったテスト
    要件が複雑な機能のテスト観点
    ■ 要件項目が多数あり複雑な機能
    ✅「関数 A」が期待通りに動く
    ✅「関数 B」は「関数 A」を使用、期待通りに動く
    ✅「関数 C」は「関数 A」を使用、期待通りに動く
    ✅「コンポーネント D」は「関数 B」を使用、期待通りに動く
    後任開発者は、安心して機能追加や変更ができる

    View full-size slide

  37. 【2】書くべきだったテスト
    コンポーネントの肥大化が防止できた
    ■ 「コンポーネントテストが書きづらくて
    …」という相談
      どこに何が書かれているか一目で理解できない
      機能追加までのリードタイムが長い

    View full-size slide

  38. 【2】書くべきだったテスト
    コンポーネントの肥大化が防止できた
    ■ 「コンポーネントテストが書きづらくて
    …」という相談
      コンポーネントを分割
      ロジックを抽出
    ✅ ロジックに対して単体テストを書く
    ✅ コンポーネントのテストは薄めに
    リリース前にモジュール分割。適所に自動テストを追加

    View full-size slide

  39. 【2】書くべきだったテスト
    コンポーネントの肥大化が防止できた
    「テストが書きづらい」という気づきが、よい設計のきっかけに
    ■ 「コンポーネントテストが書きづらくて
    …」という相談
      コンポーネントを分割
      ロジックを抽出
    ✅ ロジックに対して単体テストを書く
    ✅ コンポーネントのテストは薄めに

    View full-size slide

  40. 【2】書くべきだったテスト
    セマンティクスの不備に気づけた
    ■ 「getByRole で要素を特定できなくて…」という相談
    ・ label 要素を使用すべき箇所を見落とした
    ・ `heading` role で取得できない、p 要素の見出し
    ・ `link` role で取得できない、a 要素

    View full-size slide

  41. 【2】書くべきだったテスト
    セマンティクスの不備に気づけた
    普段からテストを書く事で気づける
    ■ 「getByRole で要素を特定できなくて…」という相談
    ・ label 要素を使用すべき箇所を見落とした
    ・ `heading` role で取得できない、p 要素の見出し
    ・ `link` role で取得できない、a 要素

    View full-size slide

  42. 【2】書くべきだったテスト
    キーボード操作の不備に気づけた
    ■ 「キーボードで操作できなくて…」という相談
    ・ フォーカスの当たらない、div 要素で作られたボタン
    ・ スペースキーを押下しても開かない、ドロップダウンメニュー

    View full-size slide

  43. 【2】書くべきだったテスト
    キーボード操作の不備に気づけた
    要件定義になかったので、実装観点になかった
    ■ 「キーボードで操作できなくて…」という相談
    ・ フォーカスの当たらない、div 要素で作られたボタン
    ・ スペースキーを押下しても開かない、ドロップダウンメニュー

    View full-size slide

  44. 【2】書くべきだったテスト
    共通 UI コンポーネントのテスト観点
    ■ デザインシステム整備前の手動テスト (目視による)
    ✅「画面 A」がデザイン通りに実装されている
    ✅「画面 B」がデザイン通りに実装されている
    ✅「画面 C」がデザイン通りに実装されている
    デザインシステムの適用で、画面が崩れないか?

    View full-size slide

  45. 【2】書くべきだったテスト
    共通 UI コンポーネントのテスト観点
    ■ デザインシステム整備が完了、各画面で使用するように
    ✅「画面 A」がデザイン通りに実装されている?
    ✅「画面 B」がデザイン通りに実装されている?
    ✅「画面 C」がデザイン通りに実装されている?
    見た目に関するリグレッションは VRT を活用

    View full-size slide

  46. 【2】書くべきだったテスト
    共通 UI コンポーネントのテスト観点
    ■ デザインシステム整備が完了、各画面で使用するように
    ✅「画面 A」がデザイン通りに実装されている
    ✅「画面 B」がデザイン通りに実装されている
    ✅「画面 C」がデザイン通りに実装されている
    リファクタリングが積極的に行えた

    View full-size slide

  47. 第3章
    書かなくてよかったテスト

    View full-size slide

  48. 【3】書かなくてよかったテスト
    不適切なテストサイズ
    ■ 「ブラウザの自動テストが Flaky で…」という相談
    ・ ブラウザの自動テストは Flaky になりがち
    ・ 他のテスト手法で担保できるテスト観点
    ・ 各テスト手法の、得手不得手を把握しておく必要がある

    View full-size slide

  49. 【3】書かなくてよかったテスト
    不適切なテストサイズ
    ■ 「ブラウザの自動テストが Flaky で…」という相談
    ・ ブラウザの自動テストは Flaky になりがち
    ・ 他のテスト手法で担保できるテスト観点
    ・ 各テスト手法の、得手不得手を把握しておく必要がある
    安定性 X 忠実性 = 信頼できるテスト

    View full-size slide

  50. 【3】書かなくてよかったテスト
    書かれることが目的になってしまったテスト
    ■ テストをたくさん書いたけれど…
    ・ テスト観点がない
    ・ モックばかりで意義を感じられない
    ・ 他のテストコードと同程度の量を何となく書いた

    View full-size slide

  51. 【3】書かなくてよかったテスト
    書かれることが目的になってしまったテスト
    ■ テストをたくさん書いたけれど…
    ・ テスト観点がない
    ・ モックばかりで意義を感じられない
    ・ 他のテストコードと同程度の量を何となく書いた
    目的がないのであれば「書かなくてもよい」

    View full-size slide

  52. 【3】書かなくてよかったテスト
    無理に aria 属性を付け足したテスト
    ■ 「getByRole で取得できなかったのでつい…」
    ・ テストのための符号として aria 属性を付与している
    ・ role="foo-button"と書かれているボタン(体験談)

    View full-size slide

  53. 【3】書かなくてよかったテスト
    無理に aria 属性を付け足したテスト
    ■ 「getByRole で取得できなかったのでつい…」
    ・ テストのための符号として aria 属性を付与している
    ・ role="foo-button"と書かれているボタン(体験談)
    No ARIA is better than Bad ARIA

    View full-size slide

  54. 【3】書かなくてよかったテスト
    無理に aria 属性を付け足したテスト
    ■ 「getByRole で取得できなかったのでつい…」
    ・ テストのための符号として aria 属性を付与している
    ・ role="foo-button"と書かれているボタン(体験談)
    正しくない aria 属性を付与するよりも、付与されない方がまだマシ

    View full-size slide

  55. 【3】書かなくてよかったテスト
    過剰な VRT
    ■ 「CI が遅くて…」という相談
    ・ 全コンポーネントの Storybook を対象に VRT を実施
    ・ CI が通るまでに数十分かかる
    ・ ビジュアルリグレッションが発生するケースを想定できているか?

    View full-size slide

  56. 【3】書かなくてよかったテスト
    過剰な VRT
    ■ 「CI が遅くて…」という相談
    ・ 全コンポーネントの Storybook を対象に VRT を実施
    ・ CI が通るまでに数十分かかる
    ・ ビジュアルリグレッションが発生するケースを想定できているか?
    他のテストタイプでカバーできているならば、数は減らせる

    View full-size slide

  57. まとめ
    「こういう理由で必要」と言い切れるのは、良いテストだと思います
    ■ 「書くべきだった」テスト
    ・ 仕様とコードの解像度をあげてくれるテスト
    ・ 安心してリファクタできるテスト
    ・ 自信を与えてくれるテスト

    View full-size slide

  58. まとめ
    テストの要不要を議論したり、目的を見直す機会に
    ■ 「書かなくてよかった」テスト
    ・ 書く事が目的になってしまったテスト
    ・ よくない方向に誘導してしまったテスト
    ・ 考慮なく作業的に追加されたテスト

    View full-size slide

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

    View full-size slide