LaravelのFeatureテストとUnitテストの違いを理解しよう
生徒
「Laravelのテストには種類があるって聞いたんですが、どう違うんですか?」
先生
「Laravelには主にFeatureテストとUnitテストの2種類があります。それぞれ役割が違うんですよ。」
生徒
「具体的にはどんな時にどちらを使えばいいんですか?」
先生
「それでは、2つのテストの違いと使い分け方を詳しく見ていきましょう!」
1. FeatureテストとUnitテストの基本的な違い
LaravelのテストにはFeatureテストとUnitテストという2つの種類があります。この2つはテストする範囲が大きく異なります。料理に例えると分かりやすいでしょう。Unitテストは「塩の味が正しいか」という個別の調味料をチェックすることで、Featureテストは「完成した料理全体の味が美味しいか」を確認することです。
Unitテストは単体テストとも呼ばれ、プログラムの小さな部品一つ一つが正しく動くかを確認します。例えば、計算機能だけ、文字列処理だけ、といった単独の機能をテストします。一方、Featureテストは機能テストとも呼ばれ、ユーザーが実際に操作する一連の流れが正しく動くかを確認します。例えば、ログイン画面でメールアドレスとパスワードを入力して、ログインボタンを押したら正しくホーム画面に移動するか、という全体の流れをテストします。
2. Unitテストの特徴と使い方
Unitテストは、プログラムの最小単位であるメソッド(関数)やクラスが正しく動作するかをテストします。他の部分に依存せず、独立してテストできることが特徴です。
例えば、消費税を計算するメソッドをテストしてみましょう。まず、計算用のクラスを作成します。
<?php
namespace App\Services;
class TaxCalculator
{
public function calculate($price)
{
return $price * 1.1;
}
}
このクラスをテストするUnitテストは以下のようになります。
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
use App\Services\TaxCalculator;
class TaxCalculatorTest extends TestCase
{
public function test_消費税計算が正しい()
{
$calculator = new TaxCalculator();
$result = $calculator->calculate(1000);
$this->assertEquals(1100, $result);
}
}
このテストでは、1000円の商品に消費税を加えると1100円になることを確認しています。Unitテストは実行速度が速く、問題箇所を特定しやすいというメリットがあります。データベースやネットワーク通信などの外部リソースを使わないため、短時間で大量のテストを実行できます。
3. Featureテストの特徴と使い方
Featureテストは、ユーザーの視点から見た機能全体をテストします。実際のHTTPリクエスト(ウェブページへのアクセス)をシミュレートして、アプリケーション全体が正しく動作するかを確認します。
例えば、商品一覧ページが正しく表示されるかをテストしてみましょう。
<?php
namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use App\Models\Product;
class ProductListTest extends TestCase
{
use RefreshDatabase;
public function test_商品一覧ページが表示される()
{
Product::create([
'name' => 'ノートパソコン',
'price' => 80000
]);
$response = $this->get('/products');
$response->assertStatus(200);
$response->assertSee('ノートパソコン');
}
}
このテストでは、商品をデータベースに登録してから商品一覧ページにアクセスし、ページが正しく表示されることと、登録した商品名が画面に表示されることを確認しています。assertSeeメソッドは、指定した文字列が画面に表示されているかをチェックします。Featureテストは実際のユーザー体験に近いテストができるため、バグを見つけやすいという利点があります。
4. テストファイルの保存場所の違い
LaravelのプロジェクトではFeatureテストとUnitテストで保存場所が異なります。これはテストの種類を明確に区別するためです。
Unitテストの保存場所
tests/Unit/フォルダに保存します
Featureテストの保存場所
tests/Feature/フォルダに保存します
テストファイルを作成するときは、Laravelのartisanコマンドを使うと自動的に正しい場所に作成されます。Unitテストを作る場合は--unitオプションを付けます。
php artisan make:test TaxCalculatorTest --unit
Featureテストを作る場合は、オプションなしでコマンドを実行します。
php artisan make:test ProductListTest
このようにコマンド一つで、正しいフォルダに正しい形式のテストファイルが作成されるため、初心者でも迷わずテストを始められます。
5. テスト実行速度とテスト範囲の関係
FeatureテストとUnitテストでは、実行速度が大きく異なります。これはテスト範囲の違いによるものです。
Unitテストは小さな部品だけをテストするため、1つのテストが0.01秒程度で終わります。100個のUnitテストを実行しても1秒程度です。これは、データベースへの接続やファイルの読み書きなど、時間のかかる処理を含まないためです。
一方、Featureテストはアプリケーション全体を動かすため、1つのテストに0.1秒から1秒程度かかることがあります。データベースへのアクセス、ルーティング処理、ビューのレンダリング(画面の生成)など、多くの処理が含まれるためです。
| 比較項目 | Unitテスト | Featureテスト |
|---|---|---|
| 実行速度 | 非常に速い | やや遅い |
| テスト範囲 | 単一メソッドやクラス | 機能全体の流れ |
| 依存関係 | 独立している | 多くの部品に依存 |
| バグ特定 | しやすい | やや難しい |
理想的なテスト構成は、Unitテストを多く書いて基礎を固め、重要な機能についてFeatureテストを書く形です。これをテストピラミッドと呼びます。ピラミッドの土台となる多数のUnitテストと、頂点となる少数のFeatureテストで、効率的にアプリケーションの品質を保証できます。
6. データベースを使うテストの違い
データベースを使う場合、FeatureテストとUnitテストでアプローチが異なります。Featureテストでは実際にデータベースを使ってテストすることが一般的ですが、Unitテストではできるだけデータベースを使わないようにします。
Featureテストでデータベースを使う例を見てみましょう。
<?php
namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use App\Models\User;
class UserRegistrationTest extends TestCase
{
use RefreshDatabase;
public function test_ユーザー登録ができる()
{
$response = $this->post('/register', [
'name' => '田中花子',
'email' => 'tanaka@example.com',
'password' => 'password123',
'password_confirmation' => 'password123'
]);
$this->assertDatabaseHas('users', [
'name' => '田中花子',
'email' => 'tanaka@example.com'
]);
$response->assertRedirect('/home');
}
}
RefreshDatabaseトレイトを使うと、テスト実行後に自動的にデータベースがリセットされます。トレイトとは、複数のクラスで共通して使える機能をまとめたものです。これにより、テスト同士が干渉せず、常に同じ状態からテストを開始できます。
Unitテストでは、データベースの代わりにモック(偽物)を使うことが推奨されます。モックとは、実際のデータベースの代わりに動作する偽のオブジェクトです。これにより、テストが高速になり、外部システムへの依存を減らせます。
7. どちらのテストを先に書くべきか
初心者の方がよく迷うのが、FeatureテストとUnitテストのどちらを先に書くべきかという問題です。これには2つの考え方があります。
一つ目はアウトサイドインと呼ばれる方法です。これは外側(Feature)から内側(Unit)へ向かってテストを書く方法です。まずFeatureテストで全体の動作を確認してから、細かい部分をUnitテストで確認していきます。この方法は、ユーザー視点で重要な機能から開発を進められるメリットがあります。
二つ目はインサイドアウトと呼ばれる方法です。これは内側(Unit)から外側(Feature)へ向かってテストを書く方法です。まず小さな部品をUnitテストで確認してから、それらを組み合わせた全体の動作をFeatureテストで確認します。この方法は、基礎となる部品の品質を確保してから全体を構築できるメリットがあります。
どちらの方法を選ぶかは、プロジェクトの特性や個人の好みによります。小規模なプロジェクトや初心者の方は、理解しやすいアウトサイドインから始めることをおすすめします。実際にユーザーが使う機能をテストすることで、モチベーションも維持しやすくなります。
8. 実践的なテストの書き分け例
実際の開発では、機能に応じてFeatureテストとUnitテストを使い分けます。例えば、ECサイトの注文機能を開発する場合を考えてみましょう。
まず、注文金額を計算する部分はUnitテストで確認します。これは単純な計算処理なので、独立してテストできます。
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
use App\Services\OrderCalculator;
class OrderCalculatorTest extends TestCase
{
public function test_送料込みの合計金額が正しい()
{
$calculator = new OrderCalculator();
$subtotal = 5000;
$shipping = 500;
$total = $calculator->calculateTotal($subtotal, $shipping);
$this->assertEquals(5500, $total);
}
}
一方、注文フォームから注文を送信して、確認画面に移動するという一連の流れはFeatureテストで確認します。
このように、計算ロジックなどのビジネスロジック(業務処理)はUnitテストで、ユーザーインターフェースを含む機能全体はFeatureテストで確認することで、効率的にテストできます。両方のテストを組み合わせることで、プログラムの品質を高いレベルで保つことができます。
9. テストを書く際の注意点
FeatureテストとUnitテストを書く際には、いくつかの注意点があります。まず、テスト名は日本語で書くと何をテストしているかが分かりやすくなります。test_商品が正しく登録されるのような名前なら、誰が見ても理解できます。
次に、一つのテストでは一つのことだけを確認するようにします。複数の機能を一つのテストで確認すると、どこで失敗したのか分かりにくくなります。これを単一責任の原則と呼びます。
また、テストは他のテストに依存しないように書きます。例えば、テストAが成功しないとテストBが実行できない、という状況は避けるべきです。各テストは独立して実行できる必要があります。
Featureテストを書く際は、テストデータの準備に時間がかかることがあります。その場合はファクトリーという機能を使うと便利です。ファクトリーは、テスト用のダミーデータを簡単に作成できる仕組みです。Laravelには標準でファクトリー機能が搭載されているので、積極的に活用しましょう。
最後に、テストのメンテナンスも重要です。プログラムを修正したら、それに対応するテストも更新する必要があります。テストが古くなって実際の動作と合わなくなると、テストの意味がなくなってしまいます。定期的にテストコードも見直して、最新の状態に保つようにしましょう。