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
JavaScriptユニットテストの理想と現実
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Sota Sugiura
April 20, 2018
Technology
7.4k
13
Share
JavaScriptユニットテストの理想と現実
Talk at 関西Node学園 梅田キャンパス 1時限目
https://nodejs.connpass.com/event/82614/
Sota Sugiura
April 20, 2018
More Decks by Sota Sugiura
See All by Sota Sugiura
内製したSlack Appで頑張るIncident Response@Waroom Meetup #1 / Incident Response with Slack App in 10X
sota1235
0
1.9k
20220926_セキュリティチームの今_for_Drs._Prime_公開用.pdf
sota1235
0
190
再発防止策を考える技術 / #phpconsen
sota1235
10
4.1k
How to choose the best npm module for your team?
sota1235
9
650
Realtime Database for high traffic production application
sota1235
7
4.2k
Road to migrate JP Web as a microservice
sota1235
4
1.7k
インターフェース再入門 / Think Interface again
sota1235
6
11k
再発防止策を考える技術 #phpconfuk_rej
sota1235
1
1.4k
Update around Firebase #io18
sota1235
3
4.4k
Other Decks in Technology
See All in Technology
AIを活用したアクセシビリティ改善フロー
degudegu2510
1
150
Cortex Codeでデータの仕事を全部Agenticにやりきろう!
gappy50
0
320
AgentCore RuntimeからS3 Filesをマウントしてみる
har1101
3
370
【PHPカンファレンス小田原2026】Webアプリケーションエンジニアにも知ってほしい オブザーバビリティ の本質
fendo181
0
320
TanStack Start エコシステムの現在地 / TanStack Start Ecosystem 2026
iktakahiro
1
350
AI時代に新卒採用、はじめました/junior-engineer-never-die
dmnlk
0
200
Strands Agents × Amazon Bedrock AgentCoreで パーソナルAIエージェントを作ろう
yokomachi
2
250
Databricks Lakebaseを用いたAIエージェント連携
daiki_akimoto_nttd
0
170
ストライクウィッチーズ2期6話のエイラの行動が許せないのでPjMの観点から何をすべきだったのかを考える
ichimichi
1
290
第26回FA設備技術勉強会 - Claude/Claude_codeでデータ分析 -
happysamurai294
0
390
Oracle AI Databaseデータベース・サービス: BaseDB/ExaDB-Dの可用性
oracle4engineer
PRO
1
150
ZOZOTOWNリプレイスでのSkills導入までの流れとこれから
zozotech
PRO
4
3k
Featured
See All Featured
Imperfection Machines: The Place of Print at Facebook
scottboms
270
14k
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
350
Side Projects
sachag
455
43k
Optimizing for Happiness
mojombo
378
71k
The Anti-SEO Checklist Checklist. Pubcon Cyber Week
ryanjones
0
110
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
61k
Chasing Engaging Ingredients in Design
codingconduct
0
160
Bootstrapping a Software Product
garrettdimon
PRO
307
120k
WCS-LA-2024
lcolladotor
0
520
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
38
2.8k
Amusing Abliteration
ianozsvald
1
150
Color Theory Basics | Prateek | Gurzu
gurzu
0
280
Transcript
JavaScriptユニットテスト ⼊入⾨門 @sota1235 関⻄西Node学園 梅梅⽥田キャンパス1時限⽬目 2018/4/20
先週の⽇日曜⽇日 • 温泉⼊入りながら資料料作ってた • 考えれば考える程「ユニットテストの書き⽅方 はプロに任せたほうがいいのでは…?サバン ナの⼈人とか」と思い始めた • もっとフロントエンドの⼈人が苦しんでそうな ことは無いかと考えた
JavaScriptユニットテスト 理理想と現実 @sota1235 関⻄西Node学園 梅梅⽥田キャンパス1時限⽬目 2018/4/20
console.log(me) • Sota Sugiura(きりん) • @sota1235 • Mercari, Inc. •
将来の夢はJavaScriptに なることです
東京から来ました
今⽇日の話 • テストについて
None
今⽇日の話 • (いろんな恩恵を受けるために)テスト(を書きたい んだけど現実問題、古いコードベースとか設計も 何もないコードがあってソースがバンドルされて るわけでもない時、私達はどうするべきなのか)に ついて • 現実のつらみからテストを書ける状態に持ってい く話をします
• テストの書き⽅方はほとんど話しません
アジェンダ • 第1章 テストの必要性 • 第2章 現実との戦い • 第3章 モジュールを切り出す
第1章 テストの必要性
なぜテストは必要か • バグ防⽌止? • 負荷確認? • イレギュラー対策?
なぜテストは必要か • バグ防⽌止? • 負荷確認? • イレギュラー対策? • →品質担保のため
品質とは • 意図した通りに動作するか • きれいなコードか • 想定していない⼊入⼒力力に耐性があるか
ユニットテストとは • あるモジュールがある単⼀一の責務を果たして いるかをテストする • 最⼩小粒度でのテストを⾏行行う • モジュール単位での品質を担保する
例例えば ͤͶ • たこ焼きが美味しいと 思った時にクリックす る”せやね”ボタン • ボタンを押すとカウン トアップして押下済み
になる
ͤͶ 1. Clickされる 2. APIと通信する 3. 通信が返ったらボタンを押下 済みCSSにする 4.
数字をAPIレスポンスを元に 更更新する
ͤͶ 1. Clickされる 2. APIと通信する 3. 通信が返ったらボタンを押下 済みCSSにする 4.
数字をAPIレスポンスを元に 更更新する それぞれをテストする
責務ごとにテストを分けると • 1テストケースがシンプルになる • モジュールが責務ごとに分かれるよう強制さ れる
責務ごとにテストを分けると • 1テストケースがシンプルになる • モジュールが責務ごとに分かれるよう強制さ れる
なぜか
汚いコードはテストが書きづらい
汚いコードはテストが書きづらい =テストを書けるようにすると綺麗になりやすい
せやねボタン document.getElementById('.seyane-button').addEventListener('click', () => { fetch('https://test.com/seyane', { method: 'POST', })
.then(res => { if (res.ok) { document.getElementById('.seyane-button').style.color = 'gray'; } return res.json() }) .then({ count } => { document.getElementById('.seyane-counter').innerHTML = count; }); });
せやねボタン document.getElementById('.seyane-button').addEventListener('click', () => { fetch('https://test.com/seyane', { method: 'POST', })
.then(res => { if (res.ok) { document.getElementById('.seyane-button').style.color = 'gray'; } return res.json() }) .then({ count } => { document.getElementById('.seyane-counter').innerHTML = count; }); });
テスト書きづらい…(´・ω・`)
なぜ書きづらいか • このファイルをrequireしたら即実⾏行行される • イベントリスナに渡される1つのfunctionがいろ んなことしてる • 分岐の数の掛け算だけテストケースが増える • 不不確定要素が多い
• APIとの通信、DOM APIのコール
これが現実だ!!!! • ⼊入社して全てのフロントエンドのコードがキ レイって早々ないと思ってる • たぶん
第2章 現実との戦い
ターゲット • テストが書くのが難しく、またテスト⽂文化も そこまで浸透していない(主観)フロントエンド に着⽬目します
なぜテストが浸透していないか • フロントエンドのコードをモジュールでばら して書けるようになったのがここ数年年 • フロントエンドの要件⾃自体が複雑化した • 複雑化したロジックを保守する必要性が増し た 考察
フロントエンド is カオス • ステートフルな世界 • UIとロジックの2つの世界 • 様々な外部要因 •
APIとの通信、ローカルストレージ
現実 is カオス • N年年物のレガシーコード • Webpack?なにそれ美味しいの? • リポジトリに威⾵風堂々と居座るjQuery1.x.0
どう⽴立ち向かうのか • 既存のロジックにユニットテストを書く • 前にコストパフォーマンスを考える
テストは書いて終わりではない • 運⽤用、保守する必要がある • ロジックが変わればテストも書き換える • 品質を担保するためのものに品質を上げる時 間を取られてはいけない
コスパを考える • テスト対象のモジュールは変更更される可能性 が⾼高くないか • テスト対象はロジックでなくUIにまつわるも のでないか
例例えば • クリックされたら消費税を計算するロジック
例例えば • クリックされたら消費税を計算するロジック • 計算ロジックだけならテストしてよさそう
例例えば • クリックされたら消費税を計算するロジック • 計算ロジックだけならテストしてよさそう • jQueryで動的に⽣生成されるDOMのclass名
例例えば • クリックされたら消費税を計算するロジック • 計算ロジックだけならテストしてよさそう • jQueryで動的に⽣生成されるDOMのclass名 • いろんな都合でclass名変わる可能性が⾼高いしそも そもDOM構造も変わりやすい
テストを書く場所の勘所 • UIのテストは無駄になることが多い • E2Eテストが難しいと⾔言われる所以 • コアのロジックは変わることが少ない • 使い回せる粒度に保てば再利利⽤用性も上がる •
「社内npmライブラリとして使い回せるか」
第3章 モジュールを切り出す
モジュールを切り出す • 現実はだいたい多くの責務を持った「何か」 がそこにいる • この章ではその何かを実際にばらしあとはユ ニットテストを書くだけ、という状態にもっ ていく
せやねボタンreturns ͤͶ • たこ焼きが美味しいと 思った時にクリックす るせやねボタン • ボタンを押すとカウン トアップして押下済み
になる
せやねボタン document.getElementById('.seyane-button').addEventListener('click', () => { fetch('https://test.com/seyane', { method: 'POST', })
.then(res => { if (res.ok) { document.getElementById('.seyane-button').style.color = 'gray'; } return res.json() }) .then({ count } => { document.getElementById('.seyane-counter').innerHTML = count; }); });
ͤͶ 1. Clickされる 2. APIと通信する 3. 通信が返ったらボタンを押下 済みCSSにする 4.
数字をAPIレスポンスを元に 更更新する
ユーザインタラクションとロジックを 分離する
1. Clickされる 2. APIと通信する 3. 通信が返ったらボタンを押下 済みCSSにする 4. 数字をAPIレスポンスを元に 更更新する
Ǽ✣nj ƺȉǕǿDžǍǽȉ ȃǎǙDž
分離する function onSeyanaButtonClick() { fetch('https://test.com/seyane', { method: 'POST', }) .then(res
=> { if (res.ok) { document.getElementById('.seyane-button').style.color = 'gray'; } return res.json() }) .then({ count } => { document.getElementById('.seyane-counter').innerHTML = count; }); } document.getElementById('.seyane-button').addEventListener('click', onSeyanaButtonClick);
分離する function onSeyanaButtonClick() { fetch('https://test.com/seyane', { method: 'POST', }) .then(res
=> { if (res.ok) { document.getElementById('.seyane-button').style.color = 'gray'; } return res.json() }) .then({ count } => { document.getElementById('.seyane-counter').innerHTML = count; }); } document.getElementById('.seyane-button').addEventListener('click', onSeyanaButtonClick); Ǽ✣njƺȉǕǿDžǍǽȉ ȃǎǙDž
ロジックを分離する
1. Clickされる 2. APIと通信する 3. 通信が返ったらボタンを押下 済みCSSにする 4. 数字をAPIレスポンスを元に 更更新する
Ǽ✣nj ƺȉǕǿDžǍǽȉ ȃǎǙDž
ロジックの分離 function onSeyanaButtonClick() { fetch('https://test.com/seyane', { method: 'POST', }) .then(res
=> { if (res.ok) { document.getElementById('.seyane- button').style.color = 'gray'; } return res.json() }) .then({ count } => { document .getElementById(‘.seyane-counter') .innerHTML = count; }); } 1. Clickされる 2. APIと通信する 3. 通信が返ったらボタン を押下済みCSSにする 4. 数字をAPIレスポンス を元に更更新する
ロジックの分離 function onSeyanaButtonClick() { fetch('https://test.com/seyane', { method: 'POST', }) .then(res
=> { if (res.ok) { document.getElementById('.seyane- button').style.color = 'gray'; } return res.json() }) .then({ count } => { document .getElementById(‘.seyane-counter') .innerHTML = count; }); } 1. Clickされる 2. APIと通信する 3. 通信が返ったらボタン を押下済みCSSにする 4. 数字をAPIレスポンス を元に更更新する
ロジックの分離 /** * @param {Response} */ function changeSeyaneButtonStatus(res) { if
(res.ok) { document .getElementById(‘.seyane-button') .style.color = 'gray'; } } 1. Clickされる 2. APIと通信する 3. 通信が返ったらボタン を押下済みCSSにする 4. 数字をAPIレスポンス を元に更更新する
ロジックの分離 function onSeyanaButtonClick() { fetch('https://test.com/seyane', { method: 'POST', }) .then(res
=> { if (res.ok) { document.getElementById('.seyane- button').style.color = 'gray'; } return res.json() }) .then({ count } => { document .getElementById(‘.seyane-counter') .innerHTML = count; }); } 1. Clickされる 2. APIと通信する 3. 通信が返ったらボタン を押下済みCSSにする 4. 数字をAPIレスポンス を元に更更新する
ロジックの分離 /** * @param {Number} */ function updateSeyaneButtonCount(count) { document
.getElementById('.seyane-counter') .innerHTML = count; } 1. Clickされる 2. APIと通信する 3. 通信が返ったらボタン を押下済みCSSにする 4. 数字をAPIレスポンス を元に更更新する
ロジックの分離 function onSeyanaButtonClick() { fetch('https://test.com/seyane', { method: 'POST', }) .then(res
=> { changeSeyaneButtonStatus(res); return res.json() }) .then({ count } => { updateSeyaneButtonCount(count); }); } 1. Clickされる 2. APIと通信する 3. 通信が返ったらボタン を押下済みCSSにする 4. 数字をAPIレスポンス を元に更更新する
テスト書けそう
現実は泥泥臭臭い • 1つのイベントリスナに詰まっていたものをま ずイベントリスナとそれ以外で分けた • その後、ロジックから2つのロジックを切り出 した • 後はファイルを分けてテストを書くだけ!
つらい現実に⽴立ち向かうには • 今あるコードを紐解いていく • 紐解く時の鍵はUIとロジックの境⽬目 • 紐解いてfunction化、module化したものにテ ストを追加していく • 簡単なところから少しずつ、ばらしていく
余談: テストを書くという⽂文化 • もし今いるチームにテストを書く⽂文化が無い なら積極的にこのアプローチを試してほしい • 1つ簡単なサンプルがあるとみんな真似できる • 新しく追加するロジックでテストを書けるよ うになる
まとめ
まとめ • 現実はつらい • つらくてもテストには価値がある • テストを書くための審美眼を極める • UI, ロジックの境⽬目
• コスパの良さ
おまけ • 過去に社内向けにmochaのトレーニングリポ ジトリを作りました • 基礎的なテストを書けるようになりたい⽅方は どうぞ • TwitterなりIssueなりで質問待ってます
おまけ • この現実のつらさ を越えた先にある つらさについて過 去に発表したので よければどうぞ https://speakerdeck.com/sota1235/importwomotukusuruhua
ご清聴ありがとうございました