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
500
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
180
バンドルサイズを半減させた話 @Browser and UI #3
ken7253
0
81
CSS polyfill とその未来
ken7253
0
280
Browser and UI #2 HTML/ARIA
ken7253
2
350
PEPCは何を変えようとしていたのか
ken7253
3
570
Browser and UI #1 CSS
ken7253
0
180
レビューのやり方を(ちょっと)整理した話
ken7253
1
610
オーバーロード関数の話 @Mita.ts #2
ken7253
0
170
フロントエンドカンファレンス北海道参加レポート
ken7253
0
97
Other Decks in Programming
See All in Programming
OSINT for SRE: 学術論文とポストモーテムから探る システム障害の共通パターン / SRE NEXT 2026
tomoyk
1
4k
エンジニア向け会社紹介/Findy Company Profile
findyinc
6
360k
Foundation Models frameworkで画像分析
ryodeveloper
1
120
はてなアカウント基盤 State of the Union
cockscomb
1
1.3k
言語を使う側から、作る側へ。 自作 Lisp で得た新たな気づき。
andpad
0
130
Laravel Boostに学ぶ、AIにPHPを書かせる技術 〜OSSの実装から蒸留するエージェント制御の王道〜
kentaroutakeda
3
500
鹿野さんに聞く!『TypeScriptコードレシピ集』で磨く実践力
tonkotsuboy_com
4
1.1k
AI駆動開発を妨げる技術的負債の解消アプローチ / ai-refactoring-approach
minodriven
17
9.2k
分散システム、なんですぐ死んでしまうん?耐障害性を高めたいあなたのためのレジリエンスパターン入門
mshibuya
7
6.5k
JAWS-UG横浜 #102 AWSサ終供養LT会 成仏できない AWS サービスたち 〜本日、三体供養します〜
maroon1st
0
210
【やさしく解説 設計編・中級 #1】一つの車に、運転手は一人 ~ある倉庫システムの事例から~
panda728
PRO
0
180
技術記事、 専門家としてのプログラマ、 言語化
mizchi
14
7.5k
Featured
See All Featured
Practical Orchestrator
shlominoach
191
11k
Navigating the moral maze — ethical principles for Al-driven product design
skipperchong
2
420
A designer walks into a library…
pauljervisheath
211
24k
How to Get Subject Matter Experts Bought In and Actively Contributing to SEO & PR Initiatives.
livdayseo
0
150
Designing Powerful Visuals for Engaging Learning
tmiket
1
450
The Cost Of JavaScript in 2023
addyosmani
55
10k
Leadership Guide Workshop - DevTernity 2021
reverentgeek
1
320
Ethics towards AI in product and experience design
skipperchong
2
330
GraphQLとの向き合い方2022年版
quramy
50
15k
Skip the Path - Find Your Career Trail
mkilby
1
170
Why You Should Never Use an ORM
jnunemaker
PRO
61
9.9k
How Fast Is Fast Enough? [PerfNow 2025]
tammyeverts
3
660
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になる シグニチャー:関数名、関数の引数の型、返り値の型などの情報のこと