第28章:互換性テスト①:観点(何が壊れやすい?)🔍🧪

28.0 この章でできるようになること 🎯✨
この章が終わるころには、こんなことができるようになります😊💡
- 変更が入ったときに「どこが壊れやすいか」を観点でパッと洗い出せる🔎
- “互換性テスト”で見るべきポイントを、漏れなくチェックリスト化できる✅
- 変更内容から「影響」と「テスト観点」を表(マトリクス)で整理できる📋✨
- 型(コンパイル)だけじゃなく、実行時・データ・意味(セマンティクス)も含めて守れる🛡️
28.1 互換性テストってなに?🧪🤝
互換性テストはひとことで言うと、
「契約(約束)を守れてるか」を、変更のたびに確認するための“見る観点”のセットです🔍✨
ここで大事なのは、テストの種類(ユニット/統合/CDC)より先に、
**「どこが壊れやすいか」を見抜く目(観点)**を持つこと👀💕
その観点があると、レビューでもCIでも、チェックが速くて強くなります💪🔥
28.2 “壊れ方”はだいたい5種類に分けられる🧩🧨
互換性が壊れるときって、だいたいこの5パターンに落ちます👇✨
-
型で壊れる(コンパイルで落ちる) 🟦💥
- 例:関数が消えた、引数が変わった、exportが変わった
-
実行で壊れる(ランタイムで落ちる) 🧯😱
- 例:例外が増えた、Promiseがrejectする条件が変わった
-
データで壊れる(JSON/DTO/イベントpayloadが合わない) 🧾💔
- 例:必須項目が増えた、enumの値が変わった
-
意味で壊れる(静かに挙動が変わる=一番怖い) 🫥⚠️
- 例:同じ入力で違う結果、単位が変わった、並び順が変わった
-
配布/解決で壊れる(読み込み方が変わって動かない) 📦🧨
- 例:ESM/CJS、exports、エントリポイント、tsconfigのモードで崩れる
TypeScript 5.8では
--module node18のような“固定点”が入り、モジュール解決の差分が意識しやすくなっています。(TypeScript) TypeScript 5.9ではimport deferも入り、モジュールの評価タイミングまで意識領域に入ってきます。(TypeScript)
- 例:ESM/CJS、exports、エントリポイント、tsconfigのモードで崩れる
TypeScript 5.8では
💡ポイント:互換性テストは「②〜⑤」を拾えるかが勝負です🔥 型は強いけど、型だけでは守りきれない場面が普通にあります😵💫
28.3 互換性テストの“観点チェックリスト” ✅🔍(超重要)
ここからが本題です💖 変更が入ったら、まずこの観点でスキャンします🕵️♀️✨
A. 公開API面(Public Surface)🎭🚪
**「利用者が触れる場所」**が変わってないか?
- exportされてる関数/クラス/型が消えてない?🧹
- 名前変更してない?(renameは高確率で破壊)✂️
exports/エントリポイント(importパス)が変わってない?📦- HTTPなら:URL/メソッド/パスパラメータ/クエリ名が変わってない?🌐
- イベントなら:イベント名が変わってない?📣
✅ テスト観点の例
- 「以前の使い方(import + 呼び出し)がそのまま通るか」
B. 入力の契約(引数・パラメータ・バリデーション)🧾🧷
**「渡し方」**が変わると壊れます😱
- 必須引数が増えた?(破壊になりやすい)🧨
- optional → 必須 に変えた?(ほぼ破壊)💥
- 型を狭めた?(
string | number→stringなど)✂️ - 受け付ける値の範囲が変わった?(例:空文字NGにした)🚫
✅ テスト観点の例
- 「古い入力(以前OKだった値)が今も通るか」
C. 出力の契約(戻り値・レスポンス・payload)🎁📦
**「返ってくるもの」**が変わると、呼び出し側が壊れます💔
- 戻り値の型が変わった?(nullable追加/削除)🧊
Promiseになった / 同期になった(超破壊)⏱️💥- 配列の要素の並び順が変わった?(静かに壊れる)🫥⚠️
- JSONのプロパティ名が変わった?(renameは危険)🧾💣
- enumの値が増えた/減った/変わった?🔢
✅ テスト観点の例
- 「古い呼び出し側が期待する形で返るか」
- 「増えたフィールドを無視しても動くか」
D. エラーの契約(例外・エラー形式・ステータス)😵💫📛
エラーも契約です。ここが変わると運用で燃えます🔥
- 例外を投げる条件が増えた?😱
- エラーコード/メッセージの形式が変わった?🧾
- HTTPのステータス(400/401/403/404/409/500…)が変わった?🌐
try/catchの前提が変わった?🧯
✅ テスト観点の例
- 「以前の失敗ケースで、同じ分類(コード/種別)で失敗するか」
- 「呼び出し側が分岐できる情報が残ってるか」
E. 意味(セマンティクス)の契約 🧠🫥⚠️
ここが一番やばいです😇(テストしないと気づけない)
-
同じ入力で、結果の意味が変わってない?(単位、丸め、境界条件)📏
-
副作用が変わってない?(ログ、イベント発火、キャッシュ)🌀
-
“暗黙の保証”が崩れてない?
- 例:「この関数はDBを書き換えない」
- 例:「このAPIは冪等」
-
タイミングが変わってない?(遅延、評価順、非同期化)⏳ TypeScript 5.9の
import deferは「いつ実行されるか」に手を入れる文法なので、影響の観点として知っておくと強いです。(TypeScript)
✅ テスト観点の例
- 「期待する不変条件(invariant)が保たれてるか」
- 「副作用が増えてないか」
F. 配布・モジュール解決・ビルドの契約 🧱📦
地味だけど事故が多いゾーンです😵💫💣
- ESM/CJSの扱い、
exportsマップ、型定義の出し方が変わった?📦 moduleResolutionやmoduleのモード差で壊れてない? TypeScript 5.8の--module node18はnodenextと挙動差が明記されていて、互換性チェックの観点にしやすいです。(TypeScript)- “型テスト”が必要な変更じゃない?(型推論、オーバーロード、条件付き型)🟦
✅ テスト観点の例
- 「代表的な利用者の import パターンでビルドが通るか」
28.4 「変更 → 影響 → テスト観点」表テンプレ 📋✨
変更が来たら、まずこの表を1枚作るのが最強です🧠💪 (レビューの質が爆上がりします🔥)
| 変更(What)🛠️ | 困る人/壊れ方(Impact)😱 | 観点タグ(Where)🏷️ | 最低限見るテスト(How)🧪 |
|---|---|---|---|
例:getUser(id) を getUser(userId) に改名 | import/呼び出しが即死💥 | A 公開API | 型(呼び出し)+ユニット |
例:レスポンスに status を追加 | 古いクライアントは無視できればOK | C 出力 | 既存レスポンス互換 |
例:age: number を必須に | 古い送信が400になる🔥 | B 入力 / D エラー | 旧入力で通るか |
| 例:並び順を降順に変更 | 画面が静かにおかしくなる🫥 | E 意味 | 並び順の期待テスト |
🏷️観点タグのおすすめ
- A 公開API面
- B 入力
- C 出力
- D エラー
- E 意味
- F 配布/解決
28.5 “壊れやすい変更”あるある20連発 💣😇
直感で危険度を付けられるようになると強いです💪✨
💥危険度MAX(だいたい破壊)
- exportの削除・rename(A)🧨
- 必須引数の追加(B)💥
- 戻り値の同期/非同期変更(C)⏱️💣
- エラー形式の変更(D)📛
- JSONの必須項目追加(B/C)🧾🔥
- enumの既存値変更(C)🔢💥
⚠️危険(静かに壊れやすい)
- 並び順の変更(E)🫥
- デフォルト値の変更(E)🎛️
- “意味は同じ”と言い張りたくなる微妙な仕様変更(E)😇
- バリデーション強化(B/D)🚫
✅比較的安全(でも観点チェックはする)
- 任意フィールドの追加(C)➕
- enumの値追加(C)➕(ただしswitch漏れ注意⚠️)
- 新しいAPIを追加(A)✨
28.6 具体例①:TypeScriptライブラリの変更(型は通る?)🟦📦
変更内容 🛠️
parseOptions の返り値にフィールド追加、そして引数を少し整理したい…!
Before
export type Options = {
mode?: "fast" | "safe";
};
export function parseOptions(input: string, opts?: Options): {
ok: true;
value: number;
} | {
ok: false;
reason: string;
};
After(やりたい変更)
export type Options = {
mode?: "fast" | "safe";
// 追加:ログを取りたい
traceId?: string;
};
export function parseOptions(input: string, opts?: Options): {
ok: true;
value: number;
// 追加:メタ情報
meta?: { traceId?: string };
} | {
ok: false;
reason: string;
meta?: { traceId?: string };
};
観点スキャン 🔍
- A 公開API:関数名/引数の形は維持 ✅
- B 入力:optsに任意追加 ✅(安全寄り)
- C 出力:フィールド追加 ✅(無視できれば互換)
- D エラー:変化なし ✅
- E 意味:traceIdが意味を変えないなら ✅
- F 配布:変化なし ✅
“落とし穴”😵💫
呼び出し側が reason のみを取り出して JSON.stringify(result) してる場合、payloadが変わることでログ/比較がズレることがあります🫥⚠️
→ 「比較・保存・ハッシュ化」してる箇所がないかはE観点でチェック✅
28.7 具体例②:HTTP APIの変更(データとエラーが地雷)🌐🧾💣
変更内容 🛠️
レスポンスに displayName を追加して、入力の name を必須にしたい。
- ✅「出力追加」は安全寄り(C)
- ❌「入力必須化」は破壊寄り(B/D)
観点スキャン 🔍
- B 入力:必須化 → 古いクライアントが400 になりやすい🔥
- D エラー:400の形式が呼び出し側で扱えるか?
- C 出力:追加は無視できればOKだけど、クライアントの厳密パース次第⚠️
💡よくある事故:
- クライアントが「知らないフィールドが来たら落ちる」実装(厳格デコーダ)😇 → 互換性テスト観点に 「追加フィールドが来ても耐えるか」 を入れる✅
28.8 具体例③:イベント(非同期)は“意味”が壊れやすい📣🧠🫥
変更内容 🛠️
UserCreated のpayloadに source を追加したい。
- C 出力:追加は比較的安全
- E 意味:sourceの値で既存処理が変わると危険⚠️
観点としては👇
- 古いConsumerが
sourceを知らなくても動く?(C) - 「作成された」定義が変わってない?(E)
- 再送/重複イベント時の扱いが変わってない?(E:冪等性)
28.9 ミニ演習:変更→影響→観点表を作ろう✍️📋✨
次の変更案を、さっきのテンプレ表に埋めてみてね😊🌸
- 関数の戻り値を
nullを返す仕様にしたい - エラー時に
throwではなく{ ok:false }を返したい - JSONの
idをuserIdにrenameしたい - enumに値を1つ追加したい
- 並び順を「新しい順」に変更したい
ヒント💡
- renameはだいたい A or Cで破壊💥
- 並び順は Eで静かに破壊🫥⚠️
28.10 “型テスト”という観点 🟦🧪(2026の最新寄りトピック)
互換性って「動く/動かない」だけじゃなくて、型の使い心地が変わるのも大問題です😵💫
- 型推論が効かなくなった
- オーバーロードの選ばれ方が変わった
neverが出るようになった- “型的にはOKだけど、意図と違う型”になった
こういうのは 型テストで守れます🛡️✨
型テストの代表ツール(押さえ)
- tsd:
.test-d.tsを書いてexpectErrorなどで型の期待を検証できます。(GitHub) - TSTyche:
describe/testとexpectっぽい書き味で型テストを回せます(実行はせず、型チェッカーで判定)。(tstyche.org)
ここでは「観点」として知っておけばOK🙆♀️ 実際の最小構成は次章(第29章)でガッツリ組みます✅
28.11 ユニットテストの“土台”も最新寄りに押さえる🧪⚡
ユニットテストの枠組みとしては、近年は Vitest が定番コースになっています(Vite設定と噛み合うのが強い)✨ VitestはViteの解決/変換パイプラインを再利用できる、という思想が公式に説明されています。(vitest.dev) そしてVitest 4.0のリリースも公式にアナウンスされています。(vitest.dev)
(でもこの章は“観点”なので、実装は次章でOKだよ😊🫶)
28.12 AI活用:観点を漏らさないプロンプト集 🤖🧠✨
AIは「観点の列挙」と「影響の想像」がめちゃ得意です💖 (例:GitHubのCopilot系、OpenAI系のコーディング支援 など🪄)
① 変更点から“壊れ方”を列挙してもらう🔍
- 「この変更で壊れる可能性があるポイントを、A公開/B入力/C出力/Dエラー/E意味/F配布 で分類して箇条書きにして」
② “意味変更”をあぶり出す🫥⚠️
- 「この変更はセマンティクス(意味)に影響する?影響するなら、利用者視点の事故例を5つ出して」
③ “テスト観点”を表にしてもらう📋✨
- 「変更内容をもとに『変更→影響→テスト観点→最小テスト案』の表を作って」
④ “古い利用者”を想像してもらう🧟♀️➡️🙂
- 「古いクライアントが使っていそうな呼び出し例を3つ作って。その呼び出しが今も動くか検証観点もつけて」
⑤ 型の互換性を守る観点🟦
- 「この型変更で、推論・オーバーロード解決・ユニオンの縮小/拡大がどう変わる?破壊の可能性を列挙して」
28.13 よくある落とし穴 😵💫🕳️(観点で防げる!)
- “追加だから安全”と思って必須にしてしまう(B/Cで破壊)💥
- “意味は同じ”と言いながら、単位・丸め・境界が変わる(Eで静かに破壊)🫥
- エラー形式を変えて、呼び出し側の分岐が死ぬ(Dで炎上)🔥
- 配布設定(exports/モジュール)で import が死ぬ(Fで事故)📦💣
- 型が通るからOKと思って、ランタイムが落ちる(B/Dで事故)😇
28.14 この章のまとめ 🧁✨
互換性テストは「テストコードを書く前に、どこが壊れるかを見抜く観点づくり」でした🔍🧪 次の章では、この観点を “最小のテスト構成(型+ユニット)” に落として、CIで守れる形にしていきます✅✨