Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
JavaScriptユニットテストの理想と現実
Search
Sota Sugiura
April 20, 2018
Technology
7.5k
13
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
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
2k
20220926_セキュリティチームの今_for_Drs._Prime_公開用.pdf
sota1235
0
220
再発防止策を考える技術 / #phpconsen
sota1235
10
4.1k
How to choose the best npm module for your team?
sota1235
9
680
Realtime Database for high traffic production application
sota1235
7
4.3k
Road to migrate JP Web as a microservice
sota1235
4
1.8k
インターフェース再入門 / Think Interface again
sota1235
6
11k
再発防止策を考える技術 #phpconfuk_rej
sota1235
1
1.4k
Update around Firebase #io18
sota1235
3
4.5k
Other Decks in Technology
See All in Technology
空間オーディオで過去の 自分(ゴースト)と競うランニング 〜HealthKitのルートを足音に変える実装〜
nao_randd
0
230
技術的負債から考える、AI時代のエンジニアリング投資 — ビズリーチの技術的負債と向き合った経験から、変更し続けられるソフトウェアを考える/ technical-debt-con2026
visional_engineering_and_design
4
3.2k
事業課題から技術的負債に向き合う
sansantech
PRO
2
2k
EventBridge に「合流」はない ― サーバーレスのワークフローを育てるということ / No Join in EventBridge
yusukeshimizu
2
150
C#コードの結合を可視化する Roslyn解析による設計改善と リファクタリング判断
dora56
0
130
ソフトウェアDNAとクラウドエージェントのススメ
cloudace
0
110
今話題のAI「Jev」って何? 宇宙最速で学ぶ会
minorun365
PRO
29
16k
データ界隈LT祭 第1回LT登壇
taromatsui_cccmkhd
2
1.4k
The Knowledge Spine: A Machine-Executable Ontology for Governed Marketing Activation
vananth22
0
110
CLIライブラリ開発を支える技術
htnabe
0
130
Claude Code本って、 読む必要あるの?
oikon48
2
480
AI de Idea
kawaguti
PRO
2
120
Featured
See All Featured
Max Prin - Stacking Signals: How International SEO Comes Together (And Falls Apart)
techseoconnect
PRO
0
470
Public Speaking Without Barfing On Your Shoes - THAT 2023
reverentgeek
1
570
Designing Dashboards & Data Visualisations in Web Apps
destraynor
232
55k
Product Roadmaps are Hard
iamctodd
55
13k
The Anti-SEO Checklist Checklist. Pubcon Cyber Week
ryanjones
0
250
What's in a price? How to price your products and services
michaelherold
247
13k
Art, The Web, and Tiny UX
lynnandtonic
304
22k
Paper Plane
katiecoart
PRO
4
53k
Git: the NoSQL Database
bkeepers
PRO
432
67k
Designing for Performance
lara
611
70k
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
The Curse of the Amulet
leimatthew05
3
15k
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
ご清聴ありがとうございました