メインコンテンツまでスキップ

第23章:テストの独立性(順序依存を消す)🧵

独立したテストの糸

たま〜に落ちるテスト(フレーク)って、心が削れるよね…😵‍💫💦 この章はそれを防ぐために、**「どのテストも単体で動く」**状態を作る練習だよ〜💪🌸


🎯 この章のゴール

  • テストを1本だけ実行しても落ちないようにできる
  • ✅ テストの実行順が変わっても(並列でも)結果が変わらないようにできる
  • beforeEach / afterEach を「正しく」使えるようになる✨

📚 学ぶこと(超重要ポイント3つ)📌

1) 「順序依存」ってなに?🧩

テストAが先に実行される前提で、テストBがこっそり成立してる状態😇 たとえば👇

  • テストAで何かを追加 → テストBは「追加された状態」を勝手に期待してる
  • テストAでモックしたのを戻してなくて → テストBに漏れてる

こういうのは、テストを1本だけ実行した瞬間に壊れる💥


2) なぜ今すぐ直すべき?(最新の事情)⚡

Vitestは **デフォで「テストファイルは並列」**で走るよ〜🏃‍♀️🏃‍♂️💨 そして **同一ファイル内のテストは基本「定義順に順番」**だけど、その順番に依存しちゃダメ🙅‍♀️ (将来シャッフルしたり、並列設定にすると一瞬で壊れる) (Vitest)

さらに、Vitestには 実行順をランダムにできる --sequence.shuffle があるよ🎲✨ 順序依存を見つける最強のライト ensure 🔥 (Vitest)


3) 独立性の合言葉🪄

1テスト=1つの世界🌎(毎回つくり直す)

  • 共有(グローバル)状態を作らない🙅‍♀️
  • もし触ったら、必ず戻す(掃除する)🧹✨
  • 「前のテストがやってくれたはず」は禁止〜🚫💦

🧪 手を動かす:わざと順序依存を作って→直す💥➡️✅

ステップ0:わざとダメな実装(共有状態)を作る😈

src/cart.ts

export type Item = { name: string; price: number }

// ❌ モジュールの外に状態がある(共有状態)
const items: Item[] = []

export function add(item: Item) {
items.push(item)
}

export function total() {
return items.reduce((sum, x) => sum + x.price, 0)
}

src/cart.test.ts

import { describe, expect, test } from "vitest"
import { add, total } from "./cart"

describe("cart(わざと順序依存)", () => {
test("追加できる", () => {
add({ name: "coffee", price: 500 })
expect(total()).toBe(500)
})

test("合計が出る(←ここ、実は自分で準備してない)", () => {
// ❌ 追加してないのに 500 を期待してる(前のテストに依存)
expect(total()).toBe(500)
})
})

ステップ1:壊れ方を体験する😵‍💫(超大事)

  • VS Codeで 2個目のテストだけ実行してみてね👉 → たぶん落ちる!💥(totalが0のはずだから)

さらに、順序依存を炙り出すために シャッフル実行もやってみよ🎲✨ Vitestは --sequence.shuffleファイルやテストの順番をランダムにできるよ (Vitest)

package.json の scripts に追加(例):

{
"scripts": {
"test": "vitest",
"test:shuffle": "vitest run --sequence.shuffle"
}
}

そして👇を何回か実行!

npm run test:shuffle

✅ 直し方(王道):状態を「外」に出して、毎回作り直す👶✨

ステップ2:Cartを「作る関数」にする(独立性の基本)🧱

src/cart.ts をこうする👇

export type Item = { name: string; price: number }

export function createCart() {
// ✅ createCartを呼ぶたびに新しい配列(新しい世界🌎)
const items: Item[] = []

return {
add(item: Item) {
items.push(item)
},
total() {
return items.reduce((sum, x) => sum + x.price, 0)
},
}
}

テストも「毎回作る」👇

src/cart.test.ts

import { beforeEach, describe, expect, test } from "vitest"
import { createCart } from "./cart"

describe("cart(独立になった✨)", () => {
let cart: ReturnType<typeof createCart>

beforeEach(() => {
cart = createCart()
})

test("追加できる", () => {
cart.add({ name: "coffee", price: 500 })
expect(cart.total()).toBe(500)
})

test("合計が出る(自分で準備してる)", () => {
cart.add({ name: "coffee", price: 500 })
expect(cart.total()).toBe(500)
})
})

これで👇が全部通るはず!

  • どちらのテストだけ実行してもOK✅
  • npm run test:shuffle でもOK✅
  • テストの並列実行でも壊れにくい✅(ファイル並列が基本だからね) (Vitest)

🧼 よくある「漏れ」パターン集(ここ超あるある)🥹

① モックが戻ってない🎭💦

vi.spyOn したまま放置、とかね。 Vitestは afterEachvi.restoreAllMocks() が超定番✨ (または test.restoreMocks を有効化でもOK) (Vitest)

例👇

import { afterEach, vi } from "vitest"

afterEach(() => {
vi.restoreAllMocks()
})

② 環境変数・グローバルを書き換えたまま🌪️

Vitestには vi.stubEnv / vi.stubGlobal と、それを戻す vi.unstubAllEnvs / vi.unstubAllGlobals があるよ🧯✨ (Vitest)


③ ファイル間の並列で壊れる🧨

Vitestは テストファイルが並列で走るのが普通だから、 「ファイルAが先にやるはず」は通用しないよ〜🙅‍♀️ (Vitest)


🤖 AIの使いどころ(この章は“診断”が強い)🩺✨

コピペして使ってOKだよ〜💕

プロンプト例①:順序依存の原因特定

  • 「このテストが順序依存になる“共有状態”の候補を列挙して。どこで状態が漏れてる?」

プロンプト例②:修正案を3段階で

  • 「最小修正 / ふつう / 理想 の3案で直して。メリデメも書いて」

プロンプト例③:beforeEach設計

  • 「beforeEachで“毎回作り直すべきもの”と、“共有していいもの”を分けて提案して」

✅ チェックリスト(合格ライン)🎓✨

  • テスト1本だけ実行しても落ちない
  • npm run test:shuffle を何回か回しても落ちない 🎲
  • beforeAllミュータブル(書き換える)なものを共有してない
  • ✅ モック・環境変数・グローバルを触ったら、必ず戻してる🧹

🧠 2026年の“いま”メモ(最新)📌

  • Vitestは v4系が現行のガイドに載ってるよ(Getting Startedの更新も2026年1月) (Vitest)
  • 実行順ランダム化は --sequence.shuffle が公式に用意されてる 🎲 (Vitest)
  • ファイル並列・テスト順序・ライフサイクルの考え方も公式で整理されてるよ 🧪 (Vitest)

次の章(ミニ演習:カフェ会計☕️🧾)に行くと、独立性ができてる人ほど爆速で気持ちよく進むよ〜💨💖 もし今のあなたのコード(テスト)が「たまに落ちる」なら、そのテスト貼ってくれたら、どこが漏れてるか一緒に特定するよ🔍✨