Pestを使ってアーキテクチャテストをやってみる
はじめに
PestっていうPHPのテスティングフレームワークがあります。
そんなPestにはアーキテクチャテストが含まれています。
そこで今回はPestを使ってアーキテクチャテストを試してみたいと思います。
環境
- PHP 8.3.2
- Pest 2.33.4
インストール
Pestのインストールは次の通り
composer require pestphp/pest --dev --with-all-dependencies
ルールを決める
アーキテクチャテストを試すために適当にルール(依存関係など)を決めておきます。
今回は下記のようなLaravelのディレクトリ構造にServicesディレクトリを追加した状態でやってみます。
※ちなみにLaravelはバージョン10.43.0を使います。
app
├── Http
│ ├── Controllers
│ │ └── UserController.php
├── Models
│ └── User.php
└── Services
└── UserService.php
サンプルコードとルールは次の通りです。
サンプルコード
<?php
namespace App\Http\Controllers;
use App\Services\UserService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class UserController
{
public function __invoke(Request $request, UserService $service): JsonResponse
{
$service->create($request->input('name'));
return new JsonResponse([], 201);
}
}
<?php
namespace App\Services;
use App\Models\User;
class UserService
{
public function create(string $name): void
{
$user = new User();
$user->name = $name;
$user->save();
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'name',
];
}
app/Models/User.php
は初めから用意された内容を適当に削っています。
ルール
- Controllers
- クラス名の末尾はControllerにする
- Illuminate\Http\Requestが使えるのはControllersのみ
- Services
- クラス名の末尾はServiceにする
- Servicesが使えるのはControllersでのみ
- Models
- Illuminate\Database\Eloquent\Modelを継承している
- Modelsが使えるのはServicesでのみ
最終的には、
Controllers -> Services -> Models
の順番に依存している形をテストしたいと思います。
テストを書く
ルールを決めたので実際にテストを書いていきます。
Pestはテストクラスを定義する必要もないので、今回はtests/Architect.php
を作って、そこに書いていきます。
<?php
describe('Controllers', function() {
arch('クラス名の末尾は`Controller`にする')
->expect('App\Http\Controllers')
->toHaveSuffix('Controller');
arch('`Illuminate\Http\Request`が使えるのはControllersのみ')
->expect('App\Http\Controllers')
->toOnlyBeUsedIn('Illuminate\Http\Request');
});
describe('Services', function() {
arch('クラス名の末尾は`Service`にする')
->expect('App\Services')
->toHaveSuffix('Service');
arch('Servicesが使えるのはControllersでのみ')
->expect('App\Services')
->toOnlyBeUsedIn('App\Http\Controllers');
});
describe('Models', function() {
arch('`Illuminate\Database\Eloquent\Model`を継承している')
->expect('App\Models')
->toExtend('Illuminate\Database\Eloquent\Model');
arch('Modelsが使えるのはServicesでのみ')
->expect('App\Models')
->toOnlyBeUsedIn('App\Services');
});
今回使った処理を簡単に書くとこんな感じになる。
-
describe
関数を使えば関連するテストをグループ化することもできるので、今回それぞれのディレクトリでまとめた -
arch
関数を使ってルールにあったテストを書いていく-
test
関数を使っても同じことができるが、アーキテクチャテストだからarch
関数を使ってみた -
expect
メソッドを使えばルールをチェックするネームスペースを指定できる -
toHaveSuffix
メソッドでクラス名の末尾が指定した文字列か検証 -
toOnlyBeUsedIn
メソッドで指定したネームスペースがexpect
メソッドで指定した箇所でのみ使われているか検証 -
toExtend
メソッドで指定したクラスがexpect
メソッドで指定した箇所のクラスが継承しているか検証
-
テストを実行する
下記のコマンドを実行すれば、今回作成したテストの確認ができます。
% ./vendor/bin/pest tests/Architect.php
PASS Tests\Architect
✓ Controllers → クラス名の末尾はControllerにする
✓ Controllers → Illuminate\Http\Requestが使えるのはControllersのみ
✓ Services → クラス名の末尾はServiceにする
✓ Services → Servicesが使えるのはControllersでのみ
✓ Models → Illuminate\Database\Eloquent\Modelを継承している
✓ Models → Modelsが使えるのはServicesでのみ
Tests: 6 passed (6 assertions)
Duration: 0.07s
それらしく動きましたね。
試しにControllersのところでModelsを使うように変更してから実行してみます。
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class UserController
{
public function __invoke(Request $request): JsonResponse
{
$user = new User();
$user->name = $request->input('name');
$user->save();
return new JsonResponse([], 201);
}
}
% ./vendor/bin/pest tests/Architect.php
FAIL Tests\Architect
✓ Controllers → クラス名の末尾はControllerにする
✓ Controllers → Illuminate\Http\Requestが使えるのはControllersのみ
✓ Services → クラス名の末尾はServiceにする
✓ Services → Servicesが使えるのはControllersでのみ
✓ Models → Illuminate\Database\Eloquent\Modelを継承している
⨯ Models → Modelsが使えるのはServicesでのみ
────────────────────────────────────────────────────────────────────
FAILED Tests\Architect > `Models` → Modelsが使えるのはServicesでのみ ArchExpectationFailedException
Expecting 'App\Models\User' not to be used on 'App\Http\Controllers\UserController'.
at app/Http/Controllers/UserController.php:5
1▕ <?php
2▕
3▕ namespace App\Http\Controllers;
4▕
➜ 5▕ use App\Models\User;
6▕ use Illuminate\Http\JsonResponse;
7▕ use Illuminate\Http\Request;
8▕
9▕ class UserController
1 app/Http/Controllers/UserController.php:5
Tests: 1 failed, 5 passed (9 assertions)
Duration: 0.07s
無事にエラーになりましたね。
まとめ
今回はPestを使ってアーキテクチャテストを試してみました。
PHPで書けるってこともあるのでわかりやすいかなって思いました。
ただ、PHPUnitを使っているプロジェクトならアーキテクチャテストのためにPestを導入する必要もないかもしれないですが(Deptracやphpatを使う方がいいかもしれない)、テストも含めてPestを使うプロジェクトなら導入してもいいかもしれないですね。
ほかにもいろいろなルールがあるのでドキュメントを見ながら試してみたいと思います。
Discussion