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
13
7.4k
JavaScriptユニットテストの理想と現実
Talk at 関西Node学園 梅田キャンパス 1時限目
https://nodejs.connpass.com/event/82614/
Sota Sugiura
April 20, 2018
Tweet
Share
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.8k
20220926_セキュリティチームの今_for_Drs._Prime_公開用.pdf
sota1235
0
180
再発防止策を考える技術 / #phpconsen
sota1235
10
4k
How to choose the best npm module for your team?
sota1235
9
630
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.3k
Update around Firebase #io18
sota1235
3
4.4k
Other Decks in Technology
See All in Technology
AI時代のAPIファースト開発
nagix
2
620
Getting started with Google Antigravity
meteatamel
1
380
もう怖くないバックグラウンド処理 Background Tasks のすべて - Hakodate.swift #1
kantacky
0
130
Snowflakeデータ基盤で挑むAI活用 〜4年間のDataOpsの基礎をもとに〜
kaz3284
1
240
トラブルの大半は「言ってない」x「言ってない」じゃねーか!!
ichimichi
0
170
Claude Codeと駆け抜ける 情報収集と実践録
sontixyou
1
1.1k
Amazon Bedrock AgentCoreでブラウザ拡張型AI調査エージェントを開発した話 (シングルエージェント編)
nasuvitz
2
120
Goで実現する堅牢なアーキテクチャ:DDD、gRPC-connect、そしてAI協調開発の実践
fujidomoe
3
780
Bill One 開発エンジニア 紹介資料
sansan33
PRO
5
18k
器用貧乏が強みになるまで ~「なんでもやる」が導いたエンジニアとしての現在地~
kakehashi
PRO
5
600
【2026年版】生成AIによる情報システムへのインパクト
taka_aki
0
180
Exadata Fleet Update
oracle4engineer
PRO
0
1.3k
Featured
See All Featured
A Tale of Four Properties
chriscoyier
162
24k
How to Build an AI Search Optimization Roadmap - Criteria and Steps to Take #SEOIRL
aleyda
1
1.9k
Java REST API Framework Comparison - PWX 2021
mraible
34
9.2k
The Curious Case for Waylosing
cassininazir
0
260
Done Done
chrislema
186
16k
Hiding What from Whom? A Critical Review of the History of Programming languages for Music
tomoyanonymous
2
470
Six Lessons from altMBA
skipperchong
29
4.2k
AI Search: Where Are We & What Can We Do About It?
aleyda
0
7k
Heart Work Chapter 1 - Part 1
lfama
PRO
5
35k
Public Speaking Without Barfing On Your Shoes - THAT 2023
reverentgeek
1
320
Neural Spatial Audio Processing for Sound Field Analysis and Control
skoyamalab
0
190
Code Reviewing Like a Champion
maltzj
527
40k
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
ご清聴ありがとうございました