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
カスタムHooksと単体テストの共通点について
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
ken7253
May 31, 2024
Programming
510
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
カスタムHooksと単体テストの共通点について
CTOA若手エンジニアコミュニティ勉強会 #5
で話したスライドです。
ken7253
May 31, 2024
More Decks by ken7253
See All by ken7253
Firefoxにコントリビューションして得られた学び
ken7253
2
190
バンドルサイズを半減させた話 @Browser and UI #3
ken7253
0
87
CSS polyfill とその未来
ken7253
0
290
Browser and UI #2 HTML/ARIA
ken7253
2
370
PEPCは何を変えようとしていたのか
ken7253
3
590
Browser and UI #1 CSS
ken7253
0
190
レビューのやり方を(ちょっと)整理した話
ken7253
1
620
オーバーロード関数の話 @Mita.ts #2
ken7253
0
180
フロントエンドカンファレンス北海道参加レポート
ken7253
0
100
Other Decks in Programming
See All in Programming
バグを直したら useEffect が消えた
colorful12
3
780
高専キャリア LT 発表内容
crysta1221
6
5.3k
Claude CodeとAgentCore Gatewayを繋ぐ際の認証認可 / Authentication and authorization when connecting Claude Code with AgentCore Gateway
har1101
2
420
数年滞っていたダークモード対応をおよそ2週間で完了させる
chigichan24
0
100
Oxlintはいいぞ(続)
yug1224
1
500
思考垂れ流し開発 ~音声入力 × AIエージェント × 開発ハーネスによる試行錯誤~
npostring
0
590
業務時間外もAIに働いてもらう話
colorful12
3
9.5k
Hono + Inertia + React で LP を構築した話
oukayuka
2
190
DroidKaigi 2026 「個人開発という実験場: Android エンジニアが手にする4つの自由」
slashnephy
0
140
Detecting Compromised CI with eBPF and Cilium Tetragon
lizrice
0
280
KotlinConf Extended South Korea 2026 Keynote
l2hyunwoo
0
130
iOS開発×AI駆動開発 〜最近使って便利だったスキルの話〜
nogu66
0
120
Featured
See All Featured
The Curious Case for Waylosing
cassininazir
1
490
Prompt Engineering for Job Search
mfonobong
0
430
Google's AI Overviews - The New Search
badams
0
1.6k
AI in Enterprises - Java and Open Source to the Rescue
ivargrimstad
0
1.4k
エンジニアに許された特別な時間の終わり
watany
108
250k
Tell your own story through comics
letsgokoyo
1
1.1k
Embracing the Ebb and Flow
colly
88
5.2k
Design in an AI World
tapps
1
290
Dealing with People You Can't Stand - Big Design 2015
cassininazir
367
27k
Building Adaptive Systems
keathley
44
3.2k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
287
14k
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
194
17k
Transcript
カスタムHooksと単体 テストの共通点について @CTOA若手エンジニアコミュニティ勉強会 #5
技術記事を書いたりするのが趣味。 最近はReactを使ったアプリケーションを書いています。 ユーザーインターフェイスやブラウザが好き。 https://github.com/ken7253 https://zenn.dev/ken7253 https://dairoku-studio.com ken7253 Frontend developer
組み込みのHooks( useState / useEffect など)を組み合わせて作る関数 ルールは組み込みHooksと一緒 use*** という命名規則を持つ コンポーネントもしくはHooksのトップレベルでのみ実行できる サードパーティで有名なHooks
useQuery(Tanstack Query) useRouter(next/router) useAtom(jotai) 特殊な制約を持った関数ぐらいの認識でもOK https://ja.react.dev/reference/rules/rules-of-hooks Hooks(カスタムHooks)とは
関数の単体テストについて簡単に確認
例として、引数として与えられた配列を全て足し合わせる sum 関数を考える。 基本的には配列の加算 NaNがあった場合 0 として扱う(無視する) Infinity が含まれていた場合は常に Infinity
を返す この関数に対してのテストを書く想定 単体テストはどのように書くか export const sum = (array: number[]): number => { if (array.some(v => v === Infinity || v === -Infinity)) { return Infinity; } return array.reduce( (a, c) => a + (Number.isNaN(c) ? c : 0), 0 ); }
よくあるのは関数に引数を渡して、返り値を検査するパターン。 単体テストはどのように書くか describe('引数として与えられた配列を全て足し合わせるsum関数', () => { describe('計算不能な数値型が含まれている場合', () => {
test('Infinityが含まれていた場合常にInfinityを返却する', () => { /* 略 */ }); import { describe, test, expect } from "vitest"; import { sum } from "./index.ts"; describe('引数が全て有効な数値の場合', () => { test('配列を足し合わせた数値が返却される', () => { const array = [1, 2, 3, 4, 5]; const sumResult = sum(array); expect(sumResult).toBe(15); }) }); test('NaNが含まれていた場合0として扱う', () => { /* 略 */ }); }); });
このとき関数自体が純粋関数ではない場合テストが書きづらい。 下記のコードは Math.random() は実行毎に値が変わってしまうのでテストしづらい。 単体テストと純粋関数 export const sum = (array:
number[]) => { if (array.some(v => v === Infinity || v === -Infinity)) { return Infinity; } const sumAll = array.reduce( (a, c) => a + (Number.isNaN(c) ? c : 0), 0 ); return sumAll * Math.random(); }
このとき関数自体が純粋関数ではない場合テストが書きづらい。 下記のコードは Math.random() は実行毎に値が変わってしまうのでテストしづらい。 単体テストと純粋関数 export const sum = (array:
number[], randomize: number) => { return sumAll * randomize; if (array.some(v => v === Infinity || v === -Infinity)) { return Infinity; } const sumAll = array.reduce( (a, c) => a + (Number.isNaN(c) ? c : 0), 0 ); }
副作用は外部から渡して純粋関数にする。 テストをするときは randomize に固定値を入れれば保証したいロジックを検査できる。 副作用を除去してテストしやすい関数を作る export const sum = (array:
number[], randomize: number) => { return sumAll * randomize; if (array.some(v => v === Infinity || v === -Infinity)) { return Infinity; } const sumAll = array.reduce( (a, c) => a + (Number.isNaN(c) ? c : 0), 0 ); }
単体テストの考え方をHooksにも適用する
単体テストの考え方をHooksにも適用する テストしづらい副作用は外部から受け取る テストコードが簡潔になるようにI/Fを設計する
単体テストの考え方をHooksにも適用する pathname を参照元にFizzBuzzをしてその履歴をstoreに格納するHooks import { useAtom } from "jotai"; import
{ useRouter } from "next/router"; import { someAtom } from "@/store/someAtom"; export const useFizzBuzz = () => { const { pathname } = useRouter(); const [fizzBuzz, setFizzBuzz] = useAtom(someAtom); const result = parseInt(pathname, 10) % 15 === 0 ? "FizzBuzz" : parseInt(pathname, 10) % 5 === 0 ? "Fizz" : parseInt(pathname, 10) % 3 === 0 ? "Buzz" : parseInt(pathname, 10); setFizzBuzz([...fizzBuzz, result]); return fizzBuzz; };
このHooksをテストしやすいように修正してみる。 依存を整理する import { useAtom, type Atom } from "jotai";
export const useFizzBuzz = <T>(pathname: string, atom: Atom<T>) => { // atomも引数として渡す const [fizzBuzz, setFizzBuzz] = useAtom(atom); const result = parseInt(pathname, 10) % 15 === 0 ? "FizzBuzz" : parseInt(pathname, 10) % 5 === 0 ? "Fizz" : parseInt(pathname, 10) % 3 === 0 ? "Buzz" : parseInt(pathname, 10); setFizzBuzz([...fizzBuzz, result]); return fizzBuzz; };
関数とHooksのテストの共通項
どちらも副作用は外部から受け取る シグニチャーの情報が増え分かりやすい モジュールが持つ 責務が引数の数に現れる のでそれを基準にコード分割のタイミングを探る Hooks import { useAtom, type
Atom } from "jotai"; export const useFizzBuzz = <T>( pathname: string, atom: Atom<T> ) => { const [fizzBuzz, setFizzBuzz] = useAtom(atom); const result = parseInt(pathname, 10) % 15 === 0 ? "FizzBuzz" : parseInt(pathname, 10) % 5 === 0 ? "Fizz" : parseInt(pathname, 10) % 3 === 0 ? "Buzz" : parseInt(pathname, 10); setFizzBuzz([...fizzBuzz, result]); return fizzBuzz; }; Function export const sum = (array: number[], randomize: number) => { if (array.some(v => v === Infinity || v === -Infinity)) { return Infinity; } const sumAll = array.reduce( (a, c) => a + (Number.isNaN(c) ? c : 0), 0 ); return sumAll * randomize; }
まとめ Hooksのテストであっても考え方は単体テストと変わらない シグニチャーの情報量を増やして意外性のないHooksになる シグニチャー:関数名、関数の引数の型、返り値の型などの情報のこと