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
カスタムHooksと単体テストの共通点について
Search
ken7253
May 31, 2024
Programming
520
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
300
Browser and UI #2 HTML/ARIA
ken7253
2
380
PEPCは何を変えようとしていたのか
ken7253
3
600
Browser and UI #1 CSS
ken7253
0
190
レビューのやり方を(ちょっと)整理した話
ken7253
1
630
オーバーロード関数の話 @Mita.ts #2
ken7253
0
180
フロントエンドカンファレンス北海道参加レポート
ken7253
0
100
Other Decks in Programming
See All in Programming
Kiroで創り、AgentCoreで繋ぐ!AWSで実践する「AI-DLC」から「AIエージェント統合」までの最新地図
licux
4
720
個人開発基盤をまるごとCloudflareに引っ越して爆速で総合的体験を向上させた話
tinykitten
0
200
Augmenting AI with the Power of Jakarta EE
ivargrimstad
0
260
そのリトライ、死んだコネクションを使い回していませんか ── GoのHTTPクライアントとHTTP/2を実プロダクト障害から学び直す
myus4a
0
200
AHC070解法紹介
eijirou
0
130
アクセシビリティから考える情報設計
high_g_engineer
0
380
Snowflakeで業務アプリを作ろう。 Snowflakeのアプリ機能解説&実践ガイド
ayumu_yamaguchi
2
290
App Storeの外へ──日本のiOSサイドローディング入門 for iOSDC Japan 2026
yuukiw00w
0
230
Java 27新機能 / Java 27 new features
kishida
2
150
The Rails Doctrine Decade
koic
2
190
Go × SIMDで高速化するベクトル検索 ~ルーフラインモデルでSIMDが効く境界を探れ! ~
po3rin
1
2.4k
Vibes Containers 〜AIで変わるコンテナ設計と運用〜
tkikuc
3
550
Featured
See All Featured
Crafting Experiences
bethany
1
350
Context Engineering - Making Every Token Count
addyosmani
9
1.1k
WCS-LA-2024
lcolladotor
0
830
YesSQL, Process and Tooling at Scale
rocio
174
15k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
11
1k
Are puppies a ranking factor?
jonoalderson
2
3.9k
Navigating the moral maze — ethical principles for Al-driven product design
skipperchong
2
560
Facilitating Awesome Meetings
lara
57
7.1k
RailsConf 2023
tenderlove
30
1.6k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
25
2.1k
Bootstrapping a Software Product
garrettdimon
PRO
306
120k
AI in Enterprises - Java and Open Source to the Rescue
ivargrimstad
0
1.5k
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になる シグニチャー:関数名、関数の引数の型、返り値の型などの情報のこと