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
ken7253
May 31, 2024
Programming
490
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
170
バンドルサイズを半減させた話 @Browser and UI #3
ken7253
0
79
CSS polyfill とその未来
ken7253
0
280
Browser and UI #2 HTML/ARIA
ken7253
2
340
PEPCは何を変えようとしていたのか
ken7253
3
560
Browser and UI #1 CSS
ken7253
0
180
レビューのやり方を(ちょっと)整理した話
ken7253
1
610
オーバーロード関数の話 @Mita.ts #2
ken7253
0
170
フロントエンドカンファレンス北海道参加レポート
ken7253
0
93
Other Decks in Programming
See All in Programming
Creating Composable Callables in Contemporary C++
rollbear
0
170
RTSPクライアントを自作してみた話
simotin13
0
630
正しくソフトウェアを作る、前提を疑うための認知の視点 / doubt-premise
minodriven
21
7k
Honoでのサプライチェーン侵害対策 〜 3つのライブラリに学ぶ
yusukebe
7
1.4k
PHPで使える日時の表現と、その知り方 #frontend_phpcon_do
o0h
PRO
0
260
並列実装の現場、2ヶ月間実務でAIを使い倒したAIもPCも私も限界が近い
ming_ayami
0
130
キャリア迷子上等 ─ "ない道"は自分で作ればいい
16bitidol
3
2.3k
LLM本来の能力を解き放つサンドボックス技術とAI民主化への適用
yukukotani
3
4.5k
「AIで開発し、AIを届ける」をEvalでつなぐ 〜AIネイティブに始めるプロダクト開発の実践〜 / Connecting "Develop with AI, deliver AI" with Eval
rkaga
4
5.4k
AIで効率化できた業務・日常
ochtum
0
150
A2UI という光を覗いてみる
satohjohn
1
160
The NotImplementedError Problem in Ruby
koic
1
940
Featured
See All Featured
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
9
1.4k
We Analyzed 250 Million AI Search Results: Here's What I Found
joshbly
1
1.4k
Facilitating Awesome Meetings
lara
57
7k
A designer walks into a library…
pauljervisheath
211
24k
Agile Leadership in an Agile Organization
kimpetersen
PRO
0
170
Leveraging Curiosity to Care for An Aging Population
cassininazir
1
280
The #1 spot is gone: here's how to win anyway
tamaranovitovic
3
1.1k
Conquering PDFs: document understanding beyond plain text
inesmontani
PRO
4
2.8k
Winning Ecommerce Organic Search in an AI Era - #searchnstuff2025
aleyda
1
2.1k
The agentic SEO stack - context over prompts
schlessera
0
820
Have SEOs Ruined the Internet? - User Awareness of SEO in 2025
akashhashmi
0
370
Scaling GitHub
holman
464
140k
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になる シグニチャー:関数名、関数の引数の型、返り値の型などの情報のこと