アーキテクチャテスト

アーキテクチャテストにより、アプリケーションがアーキテクチャのルールセットに準拠しているかどうかをテストする期待値を指定でき、クリーンで持続可能なコードベースを維持できます。期待値は、相対的な名前空間、完全修飾された名前空間、または関数名によって決定されます。

アーキテクチャルールを定義する方法の例を次に示します

1arch()
2 ->expect('App')
3 ->toUseStrictTypes()
4 ->not->toUse(['die', 'dd', 'dump']);
5 
6arch()
7 ->expect('App\Models')
8 ->toBeClasses()
9 ->toExtend('Illuminate\Database\Eloquent\Model')
10 ->toOnlyBeUsedIn('App\Repositories')
11 ->ignoring('App\Models\User');
12 
13arch()
14 ->expect('App\Http')
15 ->toOnlyBeUsedIn('App\Http');
16 
17arch()->preset()->php();
18arch()->preset()->security()->ignoring('md5');

では、アーキテクチャテストで利用可能なさまざまなメソッドと修飾子について考えてみましょう。このセクションでは、次の内容を学びます

  • 期待値: 粒度の高いアーキテクチャルールを指定できます。
  • プリセット: 事前に定義された粒度の高いアーキテクチャルールのセットを使用できます。
  • 修飾子: 特定のタイプのファイル、クラス、関数、またはコード行を除外または無視できます。

期待値

粒度の高い期待値により、アプリケーションに特定のアーキテクチャルールを定義できます。使用可能な期待値を次に示します

toBeAbstract()

toBeAbstract()メソッドは、特定の名前空間内のすべてのクラスが抽象であることを確認するために使用できます。

1arch('app')
2 ->expect('App\Models')
3 ->toBeAbstract();

toBeClasses()

toBeClasses()メソッドは、特定の名前空間内のすべてのファイルがクラスであることを確認するために使用できます。

1arch('app')
2 ->expect('App\Models')
3 ->toBeClasses();

toBeEnums()

toBeEnums()メソッドは、特定の名前空間内のすべてのファイルが列挙型であることを確認するために使用できます。

1arch('app')
2 ->expect('App\Enums')
3 ->toBeEnums();

toBeIntBackedEnums()

toBeIntBackedEnums()メソッドは、特定の名前空間内のすべての列挙型がintで支えられていることを保証するために使用できます。

1arch('app')
2 ->expect('App\Enums')
3 ->toBeIntBackedEnums();

toBeInterfaces()

toBeInterfaces()メソッドは、特定の名前空間内のすべてのファイルがインターフェースであることを保証するために使用できます。

1arch('app')
2 ->expect('App\Contracts')
3 ->toBeInterfaces();

toBeInvokable()

toBeInvokable()メソッドは、特定の名前空間内のすべてのファイルが呼び出し可能であることを保証するために使用できます。

1arch('app')
2 ->expect('App\Actions')
3 ->toBeInvokable();

toBeTraits()

toBeTraits()メソッドは、特定の名前空間内のすべてのファイルがトレイトであることを保証するために使用できます。

1arch('app')
2 ->expect('App\Concerns')
3 ->toBeTraits();

toBeFinal()

toBeFinal()メソッドは、特定の名前空間内のすべてのクラスがfinalであることを保証するために使用できます。

1arch('app')
2 ->expect('App\ValueObjects')
3 ->toBeFinal();

通常、この期待はclasses()修飾子と組み合わせて使用され、特定の名前空間内のすべてのクラスがfinalであることを保証します。

1arch('app')
2 ->expect('App')
3 ->classes()
4 ->toBeFinal();

toBeReadonly()

toBeReadonly()メソッドは、特定のクラスが不変であり、ランタイムで変更できないことを保証するために使用できます。

1arch('app')
2 ->expect('App\ValueObjects')
3 ->toBeReadonly();

通常、この期待はclasses()修飾子と組み合わせて使用され、特定の名前空間内のすべてのクラスがreadonlyであることを保証します。

1arch('app')
2 ->expect('App')
3 ->classes()
4 ->toBeReadonly();

toBeStringBackedEnums()

toBeStringBackedEnums()メソッドは、特定の名前空間内のすべての列挙型が文字列で支えられていることを保証するために使用できます。

1arch('app')
2 ->expect('App\Enums')
3 ->toBeStringBackedEnums();

toBeUsed()

not修飾子は、toBeUsed()メソッドと組み合わせることで、特定のクラスまたは関数がアプリケーションによって使用されていないことを確認できます。

1arch('globals')
2 ->expect(['dd', 'dump'])
3 ->not->toBeUsed();
4 
5arch('facades')
6 ->expect('Illuminate\Support\Facades')
7 ->not->toBeUsed();

toBeUsedIn()

not修飾子をtoBeUsedIn()メソッドと組み合わせることで、特定の名前空間内で特定のクラスや関数が使用されるのを制限できます。

1arch('globals')
2 ->expect('request')
3 ->not->toBeUsedIn('App\Domain');
4 
5arch('globals')
6 ->expect('Illuminate\Http')
7 ->not->toBeUsedIn('App\Domain');

toExtend()

toExtend()メソッドは、特定の名前空間内のすべてのクラスが特定のクラスを拡張することを保証するために使用できます。

1arch('app')
2 ->expect('App\Models')
3 ->toExtend('Illuminate\Database\Eloquent\Model');

toExtendNothing()

toExtendNothing()メソッドは、特定の名前空間内のすべてのクラスが何も拡張しないことを保証するために使用できます。

1arch('app')
2 ->expect('App\ValueObjects')
3 ->toExtendNothing();

toImplement()

toImplement()メソッドは、特定の名前空間内のすべてのクラスが特定のインターフェースを実装することを保証するために使用できます。

1arch('app')
2 ->expect('App\Jobs')
3 ->toImplement('Illuminate\Contracts\Queue\ShouldQueue');

toImplementNothing()

toImplementNothing()メソッドは、特定の名前空間内のすべてのクラスが何も実装しないことを保証するために使用できます。

1arch('app')
2 ->expect('App\ValueObjects')
3 ->toImplementNothing();

toHaveMethodsDocumented()

toHaveMethodsDocumented()メソッドは、特定の名前空間内のすべてのメソッドがドキュメント化されていることを保証するために使用できます。

1arch('app')
2 ->expect('App')
3 ->toHaveMethodsDocumented();

toHavePropertiesDocumented()

toHavePropertiesDocumented() メソッドは、指定された名前空間内のすべてのプロパティがドキュメント化されていることを確認するために使用できます。

1arch('app')
2 ->expect('App')
3 ->toHavePropertiesDocumented();

toHaveAttribute()

toHaveAttribute() メソッドは、特定のクラスに特定の属性があることを確認するために使用できます。

1arch('app')
2 ->expect('App\Console\Commands')
3 ->toHaveAttribute('Symfony\Component\Console\Attribute\AsCommand');

toHaveFileSystemPermissions()

toHaveFileSystemPermissions() メソッドは、指定された名前空間内のすべてのファイルに特定のファイルシステムパーミッションがあることを確認するために使用できます。

1arch('app')
2 ->expect('App')
3 ->not->toHaveFileSystemPermissions('0777');

toHaveLineCountLessThan()

toHaveLineCountLessThan() メソッドは、指定された名前空間内のすべてのファイルの行数が指定された値未満であることを確認するために使用できます。

1arch('app')
2 ->expect('App\Models')
3 ->toHaveLineCountLessThan(100);

toHaveMethod()

toHaveMethod() メソッドは、特定のクラスに特定のメソッドがあることを確認するために使用できます。

1arch('app')
2 ->expect('App\Http\Controllers\HomeController')
3 ->toHaveMethod('index');

toHaveMethods()

toHaveMethods() メソッドは、特定のクラスに特定のメソッドがあることを確認するために使用できます。

1arch('app')
2 ->expect('App\Http\Controllers\HomeController')
3 ->toHaveMethods(['index', 'show']);

toHavePrivateMethodsBesides()

toHavePrivateMethodsBesides() メソッドは、特定のクラスに指定されたもの以外のプライベートメソッドがないことを確認するために使用できます。

1arch('app')
2 ->expect('App\Services\PaymentService')
3 ->not->toHavePrivateMethodsBesides(['doPayment']);

toHavePrivateMethods()

toHavePrivateMethods() メソッドは、特定のクラスにプライベートメソッドがないことを確認するために使用できます。

1arch('app')
2 ->expect('App\Services\PaymentService')
3 ->not->toHavePrivateMethods();

toHaveProtectedMethodsBesides()

toHaveProtectedMethodsBesides() メソッドは、特定のクラスに指定されたもの以外のprotectedメソッドがないことを確認するために使用できます。

1arch('app')
2 ->expect('App\Services\PaymentService')
3 ->not->toHaveProtectedMethodsBesides(['doPayment']);

toHaveProtectedMethods()

toHaveProtectedMethods() メソッドは、特定のクラスにprotectedメソッドがないことを確認するために使用できます。

1arch('app')
2 ->expect('App\Services\PaymentService')
3 ->not->toHaveProtectedMethods();

toHavePublicMethodsBesides()

toHavePublicMethodsBesides() メソッドは、特定のクラスに指定されたもの以外のpublicメソッドがないことを確認するために使用できます。

1arch('app')
2 ->expect('App\Services\PaymentService')
3 ->not->toHavePublicMethodsBesides(['charge', 'refund']);

toHavePublicMethods()

toHavePublicMethods() メソッドは、特定のクラスにpublicメソッドがないことを確認するために使用できます。

1arch('app')
2 ->expect('App\Services\PaymentService')
3 ->not->toHavePublicMethods();

toHavePrefix()

toHavePrefix() メソッドは、指定された名前空間内のすべてのファイルに特定のプレフィックスがあることを確認するために使用できます。

1arch('app')
2 ->expect('App\Helpers')
3 ->not->toHavePrefix('Helper');

toHaveSuffix()

toHaveSuffix()メソッドは、特定のネームスペース内のすべてのファイルに特定のサフィックスがあることを確認するために使用できます。

1arch('app')
2 ->expect('App\Http\Controllers')
3 ->toHaveSuffix('Controller');

toHaveConstructor()

このtoHaveConstructor()メソッドは、特定のネームスペース内のすべてのファイルに__constructメソッドがあることを確認するために使用できます。

1arch('app')
2 ->expect('App\ValueObjects')
3 ->toHaveConstructor();

toHaveDestructor()

このtoHaveDestructor()メソッドは、特定のネームスペース内のすべてのファイルに__destructメソッドがあることを確認するために使用できます。

1arch('app')
2 ->expect('App\ValueObjects')
3 ->toHaveDestructor();

toOnlyImplement()

toOnlyImplement()メソッドは、特定のクラスを特定のインターフェースの実装に限定するために使用できます。

1arch('app')
2 ->expect('App\Responses')
3 ->toOnlyImplement('Illuminate\Contracts\Support\Responsable');

toOnlyUse()

toOnlyUse()メソッドは、特定のクラスを特定の関数またはクラスの使用に限定するために使用できます。たとえば、モデルが合理化され、Illuminate\Databaseネームスペースにのみ依存し、キューに入れられたジョブやイベントをディスパッチしないことを確認できます。

1arch('models')
2 ->expect('App\Models')
3 ->toOnlyUse('Illuminate\Database');

toOnlyBeUsedIn()

toOnlyBeUsedIn()メソッドを使用すると、特定のクラスまたは一連のクラスの使用を、アプリケーションの特定の部分だけに制限できます。たとえば、このメソッドを使用して、モデルはリポジトリでのみ使用され、コントローラーやサービスプロバイダーでは使用されないことを確認できます。

1arch('models')
2 ->expect('App\Models')
3 ->toOnlyBeUsedIn('App\Repositories');

toUse()

not修飾子をtoUse()メソッドと組み合わせることで、特定のネームスペース内のファイルが特定の関数またはクラスを使用しないことを示すことができます。

1arch('globals')
2 ->expect('App\Domain')
3 ->not->toUse('request');
4 
5arch('globals')
6 ->expect('App\Domain')
7 ->not->toUse('Illuminate\Http');

toUseStrictEquality()

toUseStrictEquality()メソッドは、特定のネームスペース内のすべてのファイルが厳密な等価性を使用することを確認するために使用できます。言い換えれば、===演算子が==演算子の代わりに使用されます。

1arch('models')
2 ->expect('App')
3 ->toUseStrictEquality();

または、特定のネームスペース内のすべてのファイルが厳密な等価性を使用しないことを確認する場合は、not修飾子を使用できます。

1arch('models')
2 ->expect('App')
3 ->not->toUseStrictEquality();

toUseTrait()

toUseTrait()メソッドは、特定のネームスペース内のすべてのファイルが特定の特性を使用することを確認するために使用できます。

1arch('models')
2 ->expect('App\Models')
3 ->toUseTrait('Illuminate\Database\Eloquent\SoftDeletes');

toUseTraits()

toUseTraits()メソッドは、特定のネームスペース内のすべてのファイルが特定の特性を使用することを確認するために使用できます。

1arch('models')
2 ->expect('App\Models')
3 ->toUseTraits(['Illuminate\Database\Eloquent\SoftDeletes', 'App\Concerns\CustomTrait']);

toUseNothing()

特定のネームスペースまたはクラスに依存関係がないことを示すには、toUseNothing()メソッドを使用できます。

1arch('value objects')
2 ->expect('App\ValueObjects')
3 ->toUseNothing();

toUseStrictTypes()

toUseStrictTypes()メソッドは、特定のネームスペース内のすべてのファイルが厳密な型を使用することを確認するために使用できます。

1arch('app')
2 ->expect('App')
3 ->toUseStrictTypes();

プリセット

場合によっては、アーキテクチャの期待値をゼロから記述するのは時間がかかることがあります、特に新しいプロジェクトに取り組んでいる場合、基本的なアーキテクチャの規則が満たされていることを確認したい場合です。

プリセットは、アプリケーションのアーキテクチャをテストするために使用できる、事前定義された期待値の細かなセットです。

php

phpプリセットは、あらゆるphpプロジェクトで使用できる事前定義された期待値のセットです。フレームワークやライブラリとは結び付けられていません。

dievar_dump、類似の関数の使用を回避し、非推奨のPHP関数を確実に使用しないようにします。

1arch()->preset()->php();

下記のソースコードで、phpプリセットに含まれるすべての期待値を確認できます。

security

securityプリセットは、あらゆるphpプロジェクトで使用できる事前定義された期待値のセットです。フレームワークやライブラリとは結び付けられていません。

evalmd5、類似の関数など、セキュリティ上の脆弱性につながる可能性のあるコードを使用しないようにします。

1arch()->preset()->security();

下記のソースコードで、securityプリセットに含まれるすべての期待値を確認できます。

laravel

laravelプリセットは、Laravelプロジェクトで使用できる事前定義された期待値のセットです。

プロジェクトの構造がよく知られているLaravelの慣習に従っていることを確認します。たとえば、コントローラにはindexshowcreatestoreeditupdatedestroyのみをパブリックメソッドとして持ち、常にControllerを末尾に付けます。

1arch()->preset()->laravel();

下記のソースコードで、laravelプリセットに含まれるすべての期待値を確認できます。

strict

strictプリセットは、あらゆるphpプロジェクトで使用できる事前定義された期待値のセットです。フレームワークやライブラリとは結び付けられていません。

すべてのファイルで厳密な型を使用し、すべてのクラスがfinalであることを確認します。

1arch()->preset()->strict();

下記のソースコードで、strictプリセットに含まれるすべての期待値を確認できます。

relaxed

relaxedプリセットは、あらゆるphpプロジェクトで使用できる事前定義された期待値のセットです。フレームワークやライブラリとは結び付けられていません。

strictプリセットの反対で、すべてのファイルで厳密な型を使用せず、すべてのクラスがfinalではないことを確認します。

1arch()->preset()->relaxed();

下記のソースコードで、relaxedプリセットに含まれるすべての期待値を確認できます。

custom

customプリセットは、arch()メソッドを使用して細かい期待値を記述できるため、通常は使用する必要はありません。ただし、独自プリセットを作成する場合は、customプリセットを使用できます。

複数のプロジェクトに共通して頻繁に使用する期待値のセットがある場合、またはプラグインを作成していてユーザーに期待値のセットを提供する場合に役立ちます。

1pest()->preset('ddd', function () {
2 return [
3 expect('Infrastructure')->toOnlyBeUsedIn('Application'),
4 expect('Domain')->toOnlyBeUsedIn('Application'),
5 ];
6});

presetメソッドでは、クロージャのコールバックの最初の引数でアプリケーションのPSR-4名前空間を使用できます。

1pest()->preset('silex', function (array $userNamespaces) {
2 dump($userNamespaces); // ['App\\']
3});

修飾子

場合によっては、特定のタイプのファイルを除外したり、特定のクラス、関数、または特定のコード行を無視したりすることなく、与えられた期待を適用したい場合があります。それには、次のメソッドを使用します。

ignoring()

アーキテクチャルールを定義する場合、ignoring()メソッドを使用して、ルール定義に含める必要がある名前空間やクラスを排除できます。

1arch()
2 ->preset()
3 ->php()
4 ->ignoring('die');
5 
6arch()
7 ->expect('Illuminate\Support\Facades')
8 ->not->toBeUsed()
9 ->ignoring('App\Providers');

場合によっては、特定のコンポーネントはネイティブPHPライブラリの一部であるため、「依存関係」とは見なされません。Pestでは、「ネイティブ」コードの定義をカスタマイズし、テスト時にそれを除外するために、何を無視するかを指定できます。

たとえば、Laravelを「依存関係」として考慮したくない場合は、beforeEach()関数の内部でarch()メソッドを使用して、「Illuminate」名前空間内のコードを無視できます。この方法により、アプリケーションの実際​​の依存関係のみに焦点を当てることができます。

1// tests/Pest.php
2pest()->beforeEach(function () {
3 $this->arch()->ignore([
4 'Illuminate',
5 ])->ignoreGlobalFunctions();
6});

classes()

classes()修飾子を使用すると、クラスのみに対する期待を制限できます。

1arch('app')
2 ->expect('App')
3 ->classes()
4 ->toBeFinal();

enums()

enums()修飾子を使用すると、列挙のみに対する期待を制限できます。

1arch('app')
2 ->expect('App\Models')
3 ->enums()
4 ->toOnlyBeUsedIn('App\Models');

interfaces()

interfaces()修飾子を使用すると、インターフェイスのみに対する期待を制限できます。

1arch('app')
2 ->expect('App')
3 ->interfaces()
4 ->toExtend('App\Contracts\Contract');

traits()

traits()修飾子を使用すると、特性のみに対する期待を制限できます。

1arch('app')
2 ->expect('App')
3 ->traits()
4 ->toExtend('App\Traits\Trait');

このセクションでは、アーキテクチャテストの実行方法について学習し、アプリケーションまたはライブラリのアーキテクチャが、指定された一連のアーキテクチャ要件を満たしていることを確認します。次に、コードのパフォーマンスをテストする方法について疑問に思ったことはありませんか?ストレステストを調べましょう。