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
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
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
Inside Stream API
skrb
1
780
メソッドのジェネリクスでGoの夢は広がるか? / Kyoto.go #65
utgwkk
3
950
1B+ /day規模のログを管理する技術
broadleaf
0
110
Creating Composable Callables in Contemporary C++
rollbear
0
170
Oxlintのカスタムルールの現況
syumai
6
1.2k
Spring Security 実践 ─ GraphQL APIで実務に役立つ 認証・認可 を学ぶ
wagyu
0
260
ECSアプリログをFireLensでコスト削減しようとしたけど諦めた話 in Fargate×Node.js
akihisaikeda
2
4.2k
はてなアカウント基盤 State of the Union
cockscomb
1
820
コンテキストの使い捨てをやめる — ビジネスルール駆動開発と miko —
ioki
0
240
RTSPクライアントを自作してみた話
simotin13
0
630
Language Server 使ってる? 〜VSCode と Zed の場合〜 / Are you using a Language Server? ~For VS Code and Zed~
handlename
0
810
スマートグラスで並列バイブコーディング
hyshu
0
260
Featured
See All Featured
Utilizing Notion as your number one productivity tool
mfonobong
4
330
Navigating the moral maze — ethical principles for Al-driven product design
skipperchong
2
400
Optimizing for Happiness
mojombo
378
71k
Designing for humans not robots
tammielis
254
26k
Hiding What from Whom? A Critical Review of the History of Programming languages for Music
tomoyanonymous
2
870
Building a Modern Day E-commerce SEO Strategy
aleyda
45
9.1k
Build your cross-platform service in a week with App Engine
jlugia
234
18k
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
21
1.5k
Building Experiences: Design Systems, User Experience, and Full Site Editing
marktimemedia
0
540
Navigating Algorithm Shifts & AI Overviews - #SMXNext
aleyda
1
1.3k
Have SEOs Ruined the Internet? - User Awareness of SEO in 2025
akashhashmi
0
370
Site-Speed That Sticks
csswizardry
13
1.2k
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になる シグニチャー:関数名、関数の引数の型、返り値の型などの情報のこと