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
39
15k
フロントエンドの書くべきだったテスト、書かなくてよかったテスト
https://offers.connpass.com/event/299909/
登壇資料
Takepepe
November 16, 2023
Tweet
Share
More Decks by Takepepe
See All by Takepepe
ServerAction で Progressive Enhancement はどこまで頑張れるか? / progressive-enhancement-with-server-action
takefumiyoshii
7
850
App Router への移行は「改善」となり得るのか?/ Can migration to App Router be an improvement
takefumiyoshii
8
3.1k
Webフロントエンドのための実践「テスト」手法 CodeZine Night #1
takefumiyoshii
24
8.4k
Next.js でリアーキテクトした話 / story-of-re-architect-with-nextjs
takefumiyoshii
12
8.7k
より速い WEB を目指す Next.js / nextjs-make-the-web-faster
takefumiyoshii
54
20k
フロントエンドの複雑さに耐えるため実践したこと / readyfor-nextjs-first
takefumiyoshii
25
11k
Redux の利点を振り返る
takefumiyoshii
26
8.6k
Type-only Migrate by AST
takefumiyoshii
1
630
- Regular expression & Type - Naming Rule Linter
takefumiyoshii
1
370
Other Decks in Programming
See All in Programming
ECS Service Connectのこれまでのアップデートと今後のRoadmapを見てみる
tkikuc
2
250
レガシーシステムにどう立ち向かうか 複雑さと理想と現実/vs-legacy
suzukihoge
14
2.2k
みんなでプロポーザルを書いてみた
yuriko1211
0
260
C++でシェーダを書く
fadis
6
4.1k
TypeScript Graph でコードレビューの心理的障壁を乗り越える
ysk8hori
2
1.1k
Jakarta EE meets AI
ivargrimstad
0
630
Realtime API 入門
riofujimon
0
150
エンジニアとして関わる要件と仕様(公開用)
murabayashi
0
290
.NET のための通信フレームワーク MagicOnion 入門 / Introduction to MagicOnion
mayuki
1
1.6k
【Kaigi on Rails 2024】YOUTRUST スポンサーLT
krpk1900
1
330
Flutterを言い訳にしない!アプリの使い心地改善テクニック5選🔥
kno3a87
1
180
Creating a Free Video Ad Network on the Edge
mizoguchicoji
0
120
Featured
See All Featured
Build The Right Thing And Hit Your Dates
maggiecrowley
33
2.4k
A Philosophy of Restraint
colly
203
16k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
506
140k
Learning to Love Humans: Emotional Interface Design
aarron
273
40k
Building Applications with DynamoDB
mza
90
6.1k
The Invisible Side of Design
smashingmag
298
50k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
280
13k
Statistics for Hackers
jakevdp
796
220k
Visualization
eitanlees
145
15k
XXLCSS - How to scale CSS and keep your sanity
sugarenia
246
1.3M
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
191
16k
Code Reviewing Like a Champion
maltzj
520
39k
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 が通るまでに数十分かかる ・ ビジュアルリグレッションが発生するケースを想定できているか? 他のテストタイプでカバーできているならば、数は減らせる
まとめ
まとめ 「こういう理由で必要」と言い切れるのは、良いテストだと思います ▪ 「書くべきだった」テスト ・ 仕様とコードの解像度をあげてくれるテスト ・ 安心してリファクタできるテスト ・ 自信を与えてくれるテスト
まとめ テストの要不要を議論したり、目的を見直す機会に ▪ 「書かなくてよかった」テスト ・ 書く事が目的になってしまったテスト ・ よくない方向に誘導してしまったテスト ・ 考慮なく作業的に追加されたテスト
ご清聴ありがとうございました