Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
フロントエンドの書くべきだったテスト、書かなくてよかったテスト
Search
Takepepe
November 16, 2023
Programming
40
17k
フロントエンドの書くべきだったテスト、書かなくてよかったテスト
https://offers.connpass.com/event/299909/
登壇資料
Takepepe
November 16, 2023
Tweet
Share
More Decks by Takepepe
See All by Takepepe
どの様にAIエージェントと 協業すべきだったのか?
takefumiyoshii
2
930
ServerAction で Progressive Enhancement はどこまで頑張れるか? / progressive-enhancement-with-server-action
takefumiyoshii
7
1.2k
App Router への移行は「改善」となり得るのか?/ Can migration to App Router be an improvement
takefumiyoshii
8
3.7k
Webフロントエンドのための実践「テスト」手法 CodeZine Night #1
takefumiyoshii
24
9.5k
Next.js でリアーキテクトした話 / story-of-re-architect-with-nextjs
takefumiyoshii
12
9.1k
より速い WEB を目指す Next.js / nextjs-make-the-web-faster
takefumiyoshii
54
21k
フロントエンドの複雑さに耐えるため実践したこと / readyfor-nextjs-first
takefumiyoshii
25
11k
Redux の利点を振り返る
takefumiyoshii
26
9k
Type-only Migrate by AST
takefumiyoshii
1
730
Other Decks in Programming
See All in Programming
Fluid Templating in TYPO3 14
s2b
0
110
[AI Engineering Summit Tokyo 2025] LLMは計画業務のゲームチェンジャーか? 最適化業務における活⽤の可能性と限界
terryu16
2
510
AI Agent Dojo #4: watsonx Orchestrate ADK体験
oniak3ibm
PRO
0
130
Pythonではじめるオープンデータ分析〜書籍の紹介と書籍で紹介しきれなかった事例の紹介〜
welliving
3
850
大規模Cloud Native環境におけるFalcoの運用
owlinux1000
0
260
コマンドとリード間の連携に対する脅威分析フレームワーク
pandayumi
1
420
疑似コードによるプロンプト記述、どのくらい正確に実行される?
kokuyouwind
0
350
Kotlin Multiplatform Meetup - Compose Multiplatform 외부 의존성 아키텍처 설계부터 운영까지
wisemuji
0
180
ThorVG Viewer In VS Code
nors
0
750
【卒業研究】会話ログ分析によるユーザーごとの関心に応じた話題提案手法
momok47
0
180
dchart: charts from deck markup
ajstarks
3
970
AI によるインシデント初動調査の自動化を行う AI インシデントコマンダーを作った話
azukiazusa1
1
570
Featured
See All Featured
How to Talk to Developers About Accessibility
jct
1
110
Digital Ethics as a Driver of Design Innovation
axbom
PRO
1
160
Design in an AI World
tapps
0
130
RailsConf 2023
tenderlove
30
1.3k
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
162
16k
Leo the Paperboy
mayatellez
4
1.3k
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
Why Your Marketing Sucks and What You Can Do About It - Sophie Logan
marketingsoph
0
63
Information Architects: The Missing Link in Design Systems
soysaucechin
0
750
The browser strikes back
jonoalderson
0
340
Abbi's Birthday
coloredviolet
1
4.5k
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
61
49k
Transcript
Takepepe #Offers_フロントエンドテスト フロントエンドの 書くべきだったテスト 書かなくてよかったテスト
自己紹介 ▪ 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 に関する処理はフロントエンドの範囲 ▪ <Link /> コンポーネントの遷移先 ▪ Router に関する処理
【2】書くべきだったテスト Router に関連するテスト観点 1 ▪ SPA フレームワーク や Web App フレームワーク(ex:
Next.js) ▪ Routing に関する処理はフロントエンドの範囲 ▪ <Link /> コンポーネントの遷移先 ▪ 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 に関連するテスト観点 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[])をとりうる
【2】書くべきだったテスト Router に関連するテスト観点 1 テストを書いていると、稀なケースに注意が向く ▪ string[] のリクエストを、どのように処理すべき? ❌ as string で握りつぶしているのでランタイムエラーに
…
【2】書くべきだったテスト Router に関連するテスト観点 1 どれを選んでも正解。どうあって欲しいかをテストコードで表明 ▪ string[] のリクエストを、どのように処理すべき? ❌ as string で握りつぶしているのでランタイムエラーに
… (案A) 配列先頭の値を参照すること (案B) 不正なリクエストとしてエラー画面を表示すること (案C) 不正なリクエストなので何も処理を行わないこと
【2】書くべきだったテスト Router に関連するテスト観点 1 A, B ,C どれが正しいか?要件定義に明記されていない -> 確認
-> テストを書く ▪ string[] のリクエストを、どのように処理すべき? ❌ as string で握りつぶしているのでランタイムエラーに … (案A) 配列先頭の値を参照すること ✅ (案B) 不正なリクエストとしてエラー画面を表示すること (案C) 不正なリクエストなので何も処理を行わないこと
【2】書くべきだったテスト Router に関連するテスト観点 1 要件定義と実装の精度があがる ▪ string[] のリクエストを、どのように処理すべき? ❌ as string で握りつぶしているのでランタイムエラーに
… (案A) 配列先頭の値を参照すること ✅ (案B) 不正なリクエストとしてエラー画面を表示すること (案C) 不正なリクエストなので何も処理を行わないこと
【2】書くべきだったテスト Router に関連するテスト観点 2 searchParams をどう扱うかは、フロントエンドの責務 ▪ /?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 が通るまでに数十分かかる ・ ビジュアルリグレッションが発生するケースを想定できているか? 他のテストタイプでカバーできているならば、数は減らせる
まとめ
まとめ 「こういう理由で必要」と言い切れるのは、良いテストだと思います ▪ 「書くべきだった」テスト ・ 仕様とコードの解像度をあげてくれるテスト ・ 安心してリファクタできるテスト ・ 自信を与えてくれるテスト
まとめ テストの要不要を議論したり、目的を見直す機会に ▪ 「書かなくてよかった」テスト ・ 書く事が目的になってしまったテスト ・ よくない方向に誘導してしまったテスト ・ 考慮なく作業的に追加されたテスト
ご清聴ありがとうございました