概要
関数型プログラミング(FP)の大きな強みの一つが、**「テストが極めて書きやすい」**という点にある。
- モック不要
- DI不要
- 環境構築不要
それはなぜ可能なのか?
答えは、純粋関数と不変な構造に基づいた設計哲学にある。
本稿では OCaml を例に、関数型プログラミングがテストファーストかつ高速な開発をどう支えるかを、
コードとともに構造的に示していく。
1. 純粋関数の定義と特性
純粋関数とは、「副作用を持たず、同じ入力には常に同じ出力を返す関数」
let add x y = x + y
- 外部依存なし
- グローバル状態なし
- 副作用なし
→ つまり 「入れたら結果が出るだけ」。これによりテストは完全にローカルで完結する。
2. テストしやすさの源泉:関数=決定論的構造
let greet name = "Hello, " ^ name ^ "!"
let () =
assert (greet "Alice" = "Hello, Alice!");
assert (greet "Bob" = "Hello, Bob!")
- テストは入力と出力の一致を確認するだけ
- 状態の初期化・モックの挿入・DBの準備は一切不要
3. 副作用を隔離できる構造
副作用が必要な場合も、関数型ではそれを境界に隔離する設計を取る。
let calc_tax amount = amount *. 1.1
let print_total amount =
let taxed = calc_tax amount in
Printf.printf "Total: %.2f\n" taxed
-
calc_tax
は純粋関数 → テスト可能 -
print_total
は副作用あり → エントリポイントに限定 - ロジックの中核はテスト可能領域にとどまる
4. モックもDIも不要な理由
機能 | 伝統的手法(命令型) | 関数型設計 |
---|---|---|
DBアクセス | モック or DIで差し替える | DBアクセス部分を引数として渡す関数にする |
ログ出力 | モック or Logger注入 | ロジックと出力を分離して関数として注入 |
設定値の注入 | Configクラスや外部ファイル | 引数として受け取る(副作用不要) |
→ 関数型では「振る舞い」も「依存」もすべて引数で制御できる。
→ DIコンテナもモックライブラリもいらない。
5. 設計判断フロー
① この関数は外部の状態に依存しているか? → YES → 純粋化または外部化できないか検討
② テストのために何かを初期化しているか? → YES → 副作用とロジックを分離できないか?
③ DIやモックが必要と感じているか? → YES → 引数で制御できないか?
④ 複雑な構造で再利用性が低下していないか? → YES → 小さな純粋関数に分解可能か?
6. 実際の開発スタイルへの応用
✅ サービス層とロジック層の分離
(* ピュアロジック層 *)
let calc_discount price rate = price *. (1.0 -. rate)
(* サービス層での副作用付き処理 *)
let handle_checkout price rate =
let final_price = calc_discount price rate in
Printf.printf "Final price: %.2f\n" final_price
→ calc_discount
は完全にテスト可能
→ handle_checkout
は副作用部分で、最小限に閉じ込められている
よくある誤解と対策
❌ テストのためにはインフラや外部を用意しないといけない
→ ✅ 関数型では テストの対象=純粋関数。外部依存は持ち込まない
❌ 純粋関数だけでは現実的な処理が書けない
→ ✅ 副作用は完全排除ではなく、制御下に置いて構造的に扱う
❌ DIは必要不可欠な技術
→ ✅ 引数による振る舞い注入が自然にできれば、DIは不要になる
結語
関数型プログラミングは、設計そのものが「テストしやすさ」へと最適化されている。
- ロジックは副作用を持たず
- 状態は変更されず
- 構造は静的に定義されている
だからこそ、DIもモックも環境依存もなく、ただ関数を呼ぶだけで検証が可能になる。
関数型の設計とは、
“ロジックを純粋に分離し、検証可能性を構造的に保証する哲学である。”