はじめに
Laravelのtests/
ディレクトリ以下にはFeature/
とUnit/
の二つのディレクトリが自動で作成されます。それぞれのディレクトリにはExampleTest.php
が最初からありますが、これら二つの違いは「素のPHPUnitのTestCase
クラスを継承している」か「Laravel用に拡張されたTestCase
クラスを継承しているか」です。
Feature/ExampleTest.php
はLaravel用に拡張されたTestCase
クラスを継承しているので、DBアクセスを含むLaravelの機能がテスト内で全て使えます。Unit/ExampleTest.php
は素のPHPUnitのTestCase
クラスを継承しているため、Laravelの機能が使えず、素のPHPコードとしてのロジックテストを書くものになっています。
Feature
テストは便利なのですが、Laravel用に拡張されている分テストの実行に時間がかかります。なので、Laravelに依存しない形で書けるテストは極力Unit
テストにするべきです。
FeatureテストとUnitテストの比較
次のようなUser Modelがあります。isAdmin()
メソッドでは、 role
カラムの値が「ADMIN」の場合にtrue
を返します。このメソッドについて、FeatureテストとUnitテストを書いて比較します。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use HasFactory, Notifiable;
protected $fillable = [
'name',
'email',
'password',
'role',
];
protected $hidden = [
'password',
'remember_token',
];
public function isAdmin(): bool
{
return $this->role === 'ADMIN';
}
}
Featureテストの場合
isAdmin()
メソッドを呼び出すと、Userモデルは内部でDBアクセスを行い、role
カラムの値をDBから読み出そうとします。なので、Featureテストの場合、実際にDBへデータを登録してからisAdmin()
メソッドを呼び出すことになります。
<?php
namespace Tests\Feature;
use App\Models\User;
use Tests\TestCase;
class UserTest extends TestCase
{
public function test_isAdmin()
{
$user = User::factory()->create(['role' => 'ADMIN']);
self::assertTrue($user->isAdmin());
}
}
実行結果はこちらです。実行時間は[0.38s]でした。
sail test tests/Feature/UserTest.php
PASS Tests\Feature\UserTest
✓ is admin
Tests: 1 passed
Time: 0.38s
Unitテストの場合
Unitテストの場合は、DBアクセスができません。では、どうするかというと、Userモデルをインスタンス化した後に、role
プロパティへ自分でデータをセットします。
<?php
namespace Tests\Unit;
use App\Models\User;
use PHPUnit\Framework\TestCase;
class UserTest extends TestCase
{
public function test_isAdmin()
{
$user = new User();
$user->role = 'ADMIN';
// これでもOK
// $user = new User(['role' => 'ADMIN']);
self::assertTrue($user->isAdmin());
}
}
実行結果はこちらです。実行時間は[0.06s]でした。Featureテストの1/6程度の時間で終わっています。
sail test tests/Unit/UserTest.php
PASS Tests\Unit\UserTest
✓ is admin
Tests: 1 passed
Time: 0.06s
まとめ
今回はFeatureテストとUnitテストの実行時間の比較を行いました。テスト対象のコードはとてもシンプルなものでしたが、それでも6倍以上の差でUnitテストの方が高速ということがわかりました。プロジェクトの全てのテストをFeatureテストにしてしまうと、少なくともこれだけ実行時間に差が出てしまうということですね。
また、実際のFeatureテストでは、裏でLaravelがよしなにやってくれていることも含めると、更に多くのLaravelの機能を使うことになると思います。そう考えるとFeatureテストとUnitテストでは6倍以上の実行時間の差が出るのではと思います。みなさん、Unitテスト書いていきましょう。そしてUnitテストが書けるような設計をしましょう。