架构测试

架构测试允许您指定期望,以测试您的应用程序是否符合一组架构规则,帮助您维护一个干净且可持续的代码库。这些期望由相对命名空间、完全限定命名空间或函数名称确定。

以下是如何定义架构规则的示例

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() 方法可用于确保指定命名空间中的所有枚举都是整型支持的。

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() 方法可用于确保给定命名空间中的所有类都是最终的。

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

请注意,通常此断言与 classes() 修饰符结合使用,以确保给定命名空间中的所有类都是最终的。

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

toBeReadonly()

toBeReadonly() 方法可用于确保某些类是不可变的,并且在运行时无法修改。

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

请注意,通常此断言与 classes() 修饰符结合使用,以确保给定命名空间中的所有类都是只读的。

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() 方法可用于确保某个类除了指定的方法之外没有其他受保护的方法。

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

toHaveProtectedMethods()

toHaveProtectedMethods() 方法可用于确保某个类没有任何受保护的方法。

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

toHavePublicMethodsBesides()

toHavePublicMethodsBesides() 方法可用于确保某个类除了指定的方法之外没有其他公共方法。

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

toHavePublicMethods()

toHavePublicMethods() 方法可用于确保某个类没有任何公共方法。

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() 方法可用于确保给定命名空间中的所有文件都使用特定的 trait。

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

toUseTraits()

toUseTraits() 方法可用于确保给定命名空间中的所有文件都使用特定的 traits。

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 项目。它不与任何框架或库耦合。

它确保您在所有文件中都使用严格类型,所有类都是最终类,等等。

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

您可以在我们的 源代码 中找到 strict 预设中包含的所有期望。

relaxed

relaxed 预设是一组预定义的期望,可用于任何 php 项目。它不与任何框架或库耦合。

它是 strict 预设的反面,确保您在所有文件中都不使用严格类型,所有类都不是最终类,等等。

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() 修饰符允许您将期望限制为仅 trait。

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

在本节中,您学习了如何执行架构测试,以确保您的应用程序或库的架构满足指定的架构要求集。接下来,您是否想知道如何测试代码的性能?让我们探索 压力测试