はじめに
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テストが書けるような設計をしましょう。