0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LaravelのUnitTest/FeatureTestの使い分け

0
Posted at

Laravelのテストについて、tests/Unittests/Featureに分かれているものの、
どっちに何を書くべきかが曖昧になりやすい。
特に以下のような疑問点はよくある。

  • RepositoryのテストはUnit? Feature?
  • ControllerのUnitTestは必要?
  • Middlewareのテストはどっち?
  • ForemRequestのUnitTestは必要?
  • LaravelのUnitTestはDBにアクセスしていいの?

この記事ではLaravelのレイヤー構造に沿って、"どの層をどのテストで保証すべきか"を整理する。

Laravel のレイヤー構造(ざっくり図解)

app/
├── Application                ← HTTP レイヤー
│   └── Http
│       ├── Controllers        ← Controller
│       ├── Middleware         ← Middleware
│       ├── Requests           ← FormRequest
│       └── Resources          ← Resource
│
├── Domain                     ← ドメイン層
│   ├── Models                 ← Entity / Eloquent Model
│   └── Repositories           ← Repository Interface
│
├── Infrastructure             ← 実装層(FeatureTest)
│   └── Repositories
│       ├── Eloquent           ← Repository 実装(DB)
│       └── Http               ← 外部 API クライアント

UnitTest とは何か

UnitTest は 「HTTP を通さず、純粋なロジックをテストする」 ためのテスト。
特徴

  • HTTP リクエストを使わない
  • DB を使わない(※Laravel では例外的に使うケースもある)
  • モックを使って依存を切り離す
  • 小さな単位のロジックを高速に検証する
    何をテストする?
  • Service / UseCase のビジネスロジック
  • 条件分岐
  • 例外処理
  • 複数 Repository の組み合わせ
  • Model のアクセサ・スコープなどのロジック

目的

  • ロジックの正しさを高速に保証すること
  • 外部依存を切り離して、壊れにくいテストにすること

FeatureTest とは何か(Laravel 実務における定義)

FeatureTest は 「HTTP レイヤーを含む、実際の動作を統合的にテストする」 ためのテスト。
特徴

  • HTTP リクエストを実際に投げる
  • DB を使う(RefreshDatabase)
  • ルーティング・ミドルウェア・FormRequest・Resource などを含めて動作確認
  • 実際のアプリの挙動に最も近いテスト
    何をテストする?
  • Controller の動作
  • FormRequest のバリデーション(422)
  • Middleware の認証・ヘッダー検証
  • Resource の JSON 形式
  • Repository の DB クエリ(Eloquent)
  • 実際の HTTP レスポンス

目的

  • アプリケーション全体の動作を保証すること
  • HTTP レイヤーの仕様を壊れにくくすること

レイヤーごとの最適なテスト戦略(結論)

レイヤー    テスト  理由
Controller Feature HTTP レイヤーの責務(ルート・ミドルウェア・FormRequest・Resource)を保証するため
FormRequest Feature バリデーションは HTTP を通して動くため
Repository Feature クエリの正しさは実 DB でしか保証できない
Service Unit ビジネスロジックを純粋にテストするため
Model Unit アクセサ・スコープなどロジックがある場合のみ
Middleware Feature HTTP レイヤーでしか動作を保証できない
Resource Feature JSON 構造は HTTP レスポンスで確認するのが自然

各レイヤーごとに、なぜそのテストが最適なのか(詳しい理由)

Controller (FeatureTestが最適)

Controller の責務は HTTP 入出力の接着剤に徹すること。
実際に保証すべき内容は次の通り。

  • ルーティングが正しく紐づいているか
  • ミドルウェアが正しく動作しているか
  • FormRequest が発火しているか
  • Resource が正しい JSON を返すか
  • ステータスコードが正しいか
    これらは HTTP を通さないと検証できないため、FeatureTest が最適。
    Controller を UnitTest すると、
    Repository・FormRequest・Resource を全部モック化する必要があり、
    「Controller の薄い責務」に対してコストが大きすぎる。

FormRequest(FeatureTest が最適)

FormRequest の責務は HTTP リクエストのバリデーション。

  • required / string / email などのルール
  • authorize() の判定
  • バリデーションエラー時の 422 レスポンス
  • エラーメッセージの JSON 形式
    これらは HTTP レイヤーでしか動作しないため、
    FeatureTest で /api/... に POST して 422 を確認するのが最も自然。
    UnitTest で FormRequest を new して validate() を叩く方法もあるが、
    実際の動作と乖離しやすく、保守性が低い。

Repository(FeatureTest が最適)

Repository の責務は DB クエリそのもの。

  • create() が正しく保存されるか
  • find() が正しいレコードを返すか
  • where 条件が正しいか
  • リレーションが eager load されるか
    これらは 実 DB を使わないと正しさを保証できない。
    Repository をモック化した UnitTest は、
    「モックが呼ばれた」ことしか検証できず、
    クエリの正しさは一切保証できない。
    そのため、Repository は FeatureTest(DBあり)が最適。

Service / UseCase(UnitTest が最適)

Service / UseCase の責務は ビジネスロジック。

  • 条件分岐
  • 例外処理
  • 複数 Repository の組み合わせ
  • イベント発火
  • トランザクション制御
    これらは DB や HTTP に依存しない純粋ロジックとして扱うべき。
    Repository をモック化し、
    「ロジックだけ」を高速にテストできる UnitTest が最適。

Model(必要なら UnitTest)

Eloquent Model はフレームワークの責務が大きいため、
基本的にはテスト不要。
ただし、次のようなロジックがある場合は UnitTest する価値がある。

  • アクセサ / ミューテタ
  • カスタムスコープ
  • 独自メソッド
  • 複雑な属性変換
    Model のロジックは DB を使わずにテストできるケースが多い。

Middleware(FeatureTest が最適)

Middleware の責務は HTTP リクエストの前後処理。

  • 認証・認可
  • ヘッダー検証
  • レスポンスの書き換え
  • リクエストの拒否(403 など)
    これらは HTTP を通さないと正しく動作しないため、
    FeatureTest が最適。
    UnitTest で Request/Response を new してテストする方法もあるが、
    実際の動作と乖離しやすい。

Resource(FeatureTest が最適)

Resource の責務は HTTP レスポンスの JSON 変換。

  • JSON の構造
  • ネストされたデータ
  • null の扱い
  • コレクションの整形
    これらは 実際の HTTP レスポンスで確認するのが最も自然。
    UnitTest で Resource::make() を叩く方法もあるが、
    Controller → Resource の流れを保証できない。

まとめ

色々説明してきたが結論としてはシンプルに以下の通り

【UnitTest】
- Service / UseCase(ビジネスロジック)
- Model のロジック(アクセサ・スコープなど)

【FeatureTest】
- Controller
- FormRequest
- Middleware
- Resource
- Repository(DBクエリ)

HTTPとDBに触れるものはFeature, それ以外はUnitに機械的に分類するだけで、
各レイヤーの責務とテストの目的が一致しており、
Laravel のアーキテクチャと完全に整合する。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?