「テストありき」で設計を考える。実装より先にテストを書く。
まだ実装がないので、テストは失敗する。入力と期待出力を明確にする。
テストが通る最小のコードを書く。「綺麗さ」より「動くこと」優先。
重複を消す、名前を変える、関数を分割。テストが緑のまま保たれることを確認。
→ 数分単位で高速にサイクルを回す
| 対象 | |
|---|---|
| ✅ | ロジック・計算・変換処理があるコード |
| ✅ | バグ修正(バグを再現するテストを先に書く) |
| ✅ | リファクタリング前(既存動作をテストで固定) |
| ❌ | UIのレイアウト調整(目視確認が主) |
| ❌ | プロトタイプ・使い捨てスクリプト |
# tests/test_price_calculator.py
# --- サイクル1: 赤 ---
def test_calculate_tax_included_price():
"""税込み価格を計算する"""
result = calculate_tax_included_price(1000, tax_rate=0.1)
assert result == 1100
# --- サイクル1: 緑 ---
# price_calculator.py
def calculate_tax_included_price(base_price: float, tax_rate: float) -> float:
return base_price * (1 + tax_rate)
# --- サイクル2: 赤 ---
def test_zero_price():
""" 가격이 0이면 0을 반환 """
result = calculate_tax_included_price(0, tax_rate=0.1)
assert result == 0
# --- サイクル3: 赤 ---
def test_negative_price_raises():
""" 음수 가격은 오류 """
import pytest
with pytest.raises(ValueError):
calculate_tax_included_price(-100, tax_rate=0.1)
// src/priceCalculator.test.ts
import { describe, it, expect } from "vitest";
import { calculateTaxIncludedPrice } from "./priceCalculator";
describe("calculateTaxIncludedPrice", () => {
it("税込み価格を計算する", () => {
expect(calculateTaxIncludedPrice(1000, 0.1)).toBe(1100);
});
it("価格が0の場合は0を返す", () => {
expect(calculateTaxIncludedPrice(0, 0.1)).toBe(0);
});
it("負の価格はエラー", () => {
expect(() => calculateTaxIncludedPrice(-100, 0.1)).toThrow();
});
});
// src/priceCalculator.ts
export function calculateTaxIncludedPrice(basePrice: number, taxRate: number): number {
if (basePrice < 0) throw new Error("Price must be non-negative");
return basePrice * (1 + taxRate);
}
| つまずき | 解決策 |
|---|---|
| 「何をテストすればいいかわからない」 | 入力と期待出力を1つ決める。1つでいい |
| 「テストが多すぎて書くのが苦痛」 | テストしにくい = 設計が悪い可能性。関数を小さく分ける |
| 「DBやAPIのテストはどう書く」 | DIPで外部依存を注入する(設計原則参照) |
| 「プライベートメソッドは?」 | 不要。公開API経由で検証する |
## 開発手法: TDD
- 実装の前にテストを書く(赤→緑→リファクタリング)
- 新機能追加時: まずテストを書き、テストが失敗することを確認してから実装
- バグ修正時: バグを再現するテストを先に書き、通るように修正
- テストツール: Python=pytest, TypeScript=vitest