Kodokon kodokon.com

质量:PHPUnit、PHPStan 与 PSR 规范

像专业人士那样为你的代码武装工具链:PHPUnit 测试、level 9 的 PHPStan 静态分析,以及 PSR 标准。

10 分钟 · 3 题

在 Kodokon 中打开本课

专家级的代码,与其说靠的是巧妙的技巧,不如说靠的是它的安全网。三根互补的支柱:测试(PHPUnit)通过运行代码来验证行为,静态分析(PHPStan)不运行任何东西就能证明各种性质,而共享标准(PSR)则让整个生态系统可以互操作。这一切都用 Composer 作为开发依赖来安装。

BASH
composer require --dev phpunit/phpunit
composer require --dev phpstan/phpstan
./vendor/bin/phpunit --testdox tests
./vendor/bin/phpstan analyse src --level=9
--testdox 选项会把测试显示成一份可读的规格说明。

从 PHPUnit 10 起,元数据是用 PHP 特性来书写的:#[DataProvider] 取代了 docblock 中的 @dataProvider 注解。一个数据提供者必须是公开且静态的,因为 PHPUnit 甚至在实例化测试类之前就会读取它。每一组数据都带一个描述性的键,在该用例失败时会原样显示出来 - 一个 accents 失败可以被瞬间定位。

PHP
<?php
declare(strict_types=1);

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

final class SlugifierTest extends TestCase
{
    #[DataProvider('provideTitles')]
    public function testSlugify(
        string $input,
        string $expected
    ): void {
        $this->assertSame($expected, slugify($input));
    }

    public static function provideTitles(): array
    {
        return [
            'simple' => [
                'Hello World', 'hello-world'
            ],
            'accents' => [
                'Déjà vu', 'deja-vu'
            ],
        ];
    }
}
测试一个在你项目别处定义的 slugify 函数。

PHPStan 按等级来划分它的严格程度,从 0 到 9(在较新的版本里还有一个 level 10):在 level 9 上,任何 mixed 值如果不先经过显式检查来收窄类型,就无法使用。它真正的威力来自那些 PHP 引擎会忽略、但分析器会校验的 docblock 类型:数组形状 array{email: string}、列表 list<int>、泛型。它们既是文档,同时又是证明。

PHP
<?php
declare(strict_types=1);

function findEmail(?array $user): string
{
    return $user['email'];
}

/**
 * @param array{email: string}|null $user
 */
function findEmailSafe(?array $user): string
{
    return $user === null
        ? 'n/a'
        : $user['email'];
}
Level 9:第一个函数会被拒绝(可能在 null 上访问、未知的键和类型),第二个则能通过。

PSR(PHP Standards Recommendations,即 PHP 标准建议)是 PHP-FIG 制定的约定。PSR-1PSR-12 规范风格:缩进、花括号、导入顺序 - 由 php-cs-fixerphpcs 自动应用。PSR-4 定义自动加载:一个命名空间前缀映射到一个目录,这让 Composer 无需手动 require 就能定位任何类。最后,那些接口类的 PSR - 用于日志器的 PSR-3、用于 HTTP 消息的 PSR-7 - 让你可以把一个库换成另一个,而不必改动使用它的代码。

知识检测

确认你已牢记本课的重点内容。

  1. 从 PHPUnit 10 起,一个被 #[DataProvider] 引用的数据提供者方法要满足什么约束?
    • 它必须是公开且静态的,因为 PHPUnit 会在实例化测试类之前就读取它
    • 它必须返回一个生成器,绝不能是数组
    • 它的名字必须和测试相同,再加上 Provider 后缀
  2. PHPStan level 9 强制要求什么?
    • 禁用 docblock,只允许使用原生类型
    • 对 mixed 的严格处理:不先收窄类型就无法使用这样的值
    • 对被分析的代码要求 100% 的测试覆盖率
  3. PSR-4 标准定义了什么?
    • 应用程序日志消息的格式
    • 命名空间前缀与目录之间的映射,供 Composer 的自动加载器使用
    • 缩进风格和花括号的摆放位置