Pest 5 为 Laravel/PHP 团队提供 Agent 代码验证框架,将安全检查从模糊的 code review 转化为可设计的验证流程,配合静态分析和人工审核。
如果你的团队正在尝试编码智能体,Pest 5 以一种有用的方式改变了讨论。它让智能体安全不再是模糊的审查文化问题,而变成了一个验证设计问题。这是一个正确的框架。智能体会继续快速生成看似合理的代码。唯一持久的答案是让正确性变得廉价可证。
对于 Laravel 和 PHP 团队来说,技术栈现在清晰多了:用 Pest 测试来守护核心业务逻辑,用静态分析来守护结构正确性,用 evals 来守护模糊行为,用人工审查来守护判断性决策。Pest 5 的意义在于:它给这些组件一个真正适合智能体辅助开发的工作流形态,而不是把 AI 当作一条独立的新奇赛道来处理。
需要避免的错误很简单:不要把一个智能体强加到一个脆弱的代码库上,然后指望代码审查来吸收风险。如果重要的规则没有被编码进去,那不是智能体制造了你的问题——它只是暴露了它。
转变:验证优先,智能体其次
大多数团队仍在用错误的方式评估智能体输出。他们从生成的 diff 开始,然后让审查者凭 mental simulation 来判断它是否安全。这不可扩展。在 AI 出现之前它就不可扩展,但智能体让失败变得显而易见——因为它们可以在人类正确推理一个变更的时间内生成十个可审查的变更。
Pest 5 推动了一个更好的操作模型。它的当前版本和文档将框架定位在更快的反馈循环、测试影响分析、一个智能体验证命令、浏览器测试,以及面向 AI 重度应用的 eval 工作流。重要的不是功能清单本身。重要的是这份清单所隐含的意思:验证应该是智能体编写代码的产品边界。
这意味着你需要明确划分职责:
单元测试和功能测试负责业务规则和回归检查。
静态分析负责类型纪律、框架误用和结构漂移。
Evals 负责更广泛的输出质量——当精确相等是错误的断言模型时。
人工审查负责架构、范围纪律、命名和产品意图。
如果你让这些边界模糊,智能体就会变成焦虑的来源。如果你把它们明确化,智能体就变成了另一个向强验收管道输送内容的生产者。
这就是为什么我认为 Pest 5 真正价值不在于"AI 支持"。而在于 Pest 终于把验证当作一种可以围绕智能体工作流塑造的东西,而不是把测试套件变成一场表演。
步骤 1:把不可协商的规则放入测试
如果一个被破坏的规则会造成金钱损失、权限泄漏、错误的状态转换或用户可见的数据损坏,它就应该放在一个常规测试里。而不是在 prompt 里。不是在审查者清单里。不是在部落知识里。
这是在让智能体接触有意义的流程之前,Laravel 代码库需要修复的第一件事。测试套件必须以可执行的形式回答:什么绝不能改变。
什么应该放在 Pest 测试里
在实践中,这些类别几乎总是属于那里:
定价和税务计算
授权和策略结果
状态机转换
验证不变量
任务、webhook 和重试的幂等性行为
下游系统使用的数据转换规则
这些不是"有则更好"的测试。它们是让智能体迭代变得安全的契约。
示例:智能体无法协商的定价逻辑
假设一个智能体重构了一个结账服务以减少重复。代码可能看起来更干净,但恰恰在关键的地方出错了。这正是狭义、明确的 Pest 测试套件发挥作用的时候。
use App\Domain\Billing\Cart;
use App\Domain\Billing\Money;
use App\Domain\Billing\PercentageDiscount;
it('never drops below zero when discounts exceed subtotal', function () {
$cart = new Cart(subtotal: Money::fromInt(5000));
$total = $cart
->applyDiscount(new PercentageDiscount(150))
->total();
expect($total->toInt())->toBe(0);
});
it('applies store credit after discount rules without producing negative totals', function () {
$cart = new Cart(subtotal: Money::fromInt(8000));
$total = $cart
->applyDiscount(new PercentageDiscount(25))
->applyStoreCredit(Money::fromInt(9000))
->total();
expect($total->toInt())->toBe(0);
});
it('keeps cents precise across discount math', function () {
$cart = new Cart(subtotal: Money::fromInt(1999));
$total = $cart
->applyDiscount(new PercentageDiscount(10))
->total();
expect($total->toInt())->toBe(1799);
});
这些测试在文学意义上并不有趣。很好。它们应该是无聊且无情的。智能体可以重构服务、替换协作者、重命名类或优化内部实现。但它不能在静默破坏金钱规则的情况下还不出现可见的失败。
保持测试套件锋利,而不是臃肿
一个常见的过度反应是用巨大的端到端覆盖来补偿 AI。这通常会让反馈循环变得更糟。
你想要的是一个具有不同响应时间的分层测试套件:
针对硬领域规则的小型单元测试
针对 Laravel 请求和策略流程的聚焦功能测试
针对数据库、队列、邮件或外部边界的有数量限制的集成测试
只在 UI 行为真正重要的地方使用浏览器测试
如果每条规则都只通过慢速浏览器或全栈流程来验证,测试影响分析就会变得不那么有用,而智能体也会失去你最初想要的速度优势。
测试套件应该对成本有明确的立场。廉价测试应该守护代价高昂的错误。
步骤 2:用静态分析尽早捕捉结构漂移
测试捕获行为。它们不能可靠地捕捉到一个服务返回了错误的形状、nullable 泄漏悄然穿过边界、集合改变了元素类型,或者一个辅助方法变成了软类型倾倒场。智能体特别擅长制造这类问题,因为代码看起来仍然很合理。
这就是为什么静态分析需要成为智能体验证的一部分,而不是你一直推迟的单独质量计划。
Pest 5 更广泛的生态系统信息在这里也很重要。官方发布材料把 Pest 与围绕验证的更强工具链联系在一起,而不仅仅是测试语法。这与现实相符:Laravel 团队需要 Pest 加上 PHPStan 加上 Larastan,而不是单独一个 Pest。
在智能体接触的地方让契约变得显式
应用服务、数据映射器和领域行为从显式形状中获益良多。一旦你把返回契约记录得足够严密,人类和工具都会更少有机会自欺欺人。
<?php
namespace App\Actions\Orders;
use App\Models\Order;
final class BuildOrderSummary
{
/**
* @return array{
* id: int,
* customer_email: string,
* currency: string,
* lines: list<array{
* sku: string,
* name: string,
* quantity: int,
* total_cents: int
* }>,
* total_cents: int
* }
*/
public function handle(Order $order): array
{
return [
'id' => $order->id,
'customer_email' => $order->customer_email,
'currency' => $order->currency,
'lines' => $order->items
->map(fn ($item) => [
'sku' => $item->sku,
'name' => $item->name,
'quantity' => $item->quantity,
'total_cents' => $item->total_cents,
])
->values()
->all(),
'total_cents' => $order->total_cents,
];
}
}
那个 docblock 不是装饰性的。它给了 PHPStan 和 Larastan 足够强大的推理依据——当智能体开始"清理"序列化器或改变下游消费者时。
没有这些契约,典型的失败模式是这样的:
智能体把整数换成了浮点数,因为它觉得方便。
集合变成了惰性的,而原来假设的是贪婪数组。
一个 nullable 属性泄漏到了邮件或队列负载逻辑中。
代码通过了随意审查,因为 diff 看起来很整洁。
静态分析是在那里快速杀死这些更改的正确地方。
一个适合智能体工作的合理 Laravel 基线
如果你想要一个实用的基准,这是一个好的起点:
在每次智能体编写的更改上运行 PHPStan 和 Larastan。
把新的 ignore 视为需要解释的技术债务,而不是例行的清理。
首先为应用服务添加返回类型、泛型集合和数组形状。
先收紧热路径,再处理边缘路径。订单、计费、认证、webhook 和导出通常值得最早关注。
重点不是达到某种抽象的纯粹级别。重点是缩小智能体可以引入静默结构衰减但仍然看起来有产出的空间。
官方参考资料值得在读者想要深入了解时链接:Pest 5 公告、Agent 插件文档、PHPStan 和 Larastan。
步骤 3:用智能体验证进行快速探测,而不是永久覆盖
Pest 智能体插件是此版本中最实用的部分之一,因为它承认了一个真实的工作流需求:有时候智能体不需要一个新的永久性测试,而是需要一个一次性的证明,来验证改动在真实测试环境中是有效的。
这与"智能体应该随便点点、希望有效"的说法完全不同。该插件的当前文档描述了一个单一的验证命令,可以直接执行后端行为,并且在安装了浏览器工具的情况下,还能驱动真实的 UI 流程。这之所以有用,正是因为它保持在验证边界内,而不是发明一个独立的 AI 沙箱。
一次性智能体验证的适用场景
这才是它最合适的位置:
控制器重构后路由仍返回 200
策略仍正确阻止了某用户类别
Livewire 或 Blade 变更后仪表盘仍正常渲染
事件接线变更后队列副作用仍然发生
表单流程仍正常工作,但你尚未决定它是否值得一个永久的浏览器测试
一次性智能体验证的不适用场景
这是团队会误用的地方:
用临时探测取代持久化测试
将一次成功的检查视为广泛正确性的证明
让智能体随机构建模糊的成功标准
因为探测"已经足够好了"就跳过常规功能测试
规则应该严格:智能体验证探测是临时证据,不是长期覆盖。如果某个行为会反复出现,就把它提升为一个真正的测试。
示例:仪表盘访问变更后的一次性探测
官方文档展示了这个模式在 --agent 命令下的形状。在实践中,Laravel 团队在做了授权或渲染变更后可能会这样使用:
./vendor/bin/pest --agent='$user = \\App\\Models\\User::factory()->create(); $this->actingAs($user)->get("/dashboard")->assertOk();'
这之所以有用,是因为它快速检查了实际的应用边界。但如果仪表盘访问规则是业务关键的,就只停在这里一次。当该流程第二次重要时,就将它转为一个提交的功能测试。
正确的思维模型:
探测一次以解除本地迭代的阻塞
将反复出现或有风险的行为提升到测试套件中
永远不要把快速证据与持久覆盖混为一谈
这个区别将让你的团队免于创建一堆无法验证的智能体传说。
第四步:将 Evals 视为行为扫描,而不是真理机器
Evals 是这个技术栈中最被误解的部分,因为团队倾向于在两个极端之间摇摆。要么因为听起来模糊而回避,要么因为听起来新潮而过度使用。
正确的用法更窄、也更有价值:evals 非常适合在精确字符串相等或单一断言不是正确测试模型时,进行广泛的行为评估。
对于将 AI 功能构建到 Laravel 应用中的团队来说,这非常重要,但对于想要跨多种情况验证行为类的智能体驱动的代码变更也是如此。
什么适合放入 evals
好的候选包括:
提示驱动的摘要质量
跨数据集的分类一致性
生成的内容是否遵循策略约束
支持助手是否正确拒绝不安全请求
代码转换是否在一组 fixture 上保留了行为
什么不适合放进去:
退款算术 n- 基于角色的授权真值
具有确定性结果的验证规则
任何你能用直接断言清晰表达的东西
如果结果是精确的和确定性的,就使用普通测试。Evals 不是对良好工程的时尚替代。
示例:评估支持助手策略边界
一个发布内部或面向客户的助手的 Laravel 团队,可能会使用数据集式案例来测试拒绝和升级行为。Pest 的 eval 工具在这里是有意义的,因为"正确"往往是关于跨多样化提示的政策 adherence,而不是一个确切的句子。
it('handles refund policy scenarios consistently', function (string $message, string $expectedLabel) {
$reply = app(SupportAgent::class)->respond($message);
$label = app(ResponsePolicyClassifier::class)->classify($reply);
expect($label)->toBe($expectedLabel);
})->with([
['I want a refund for an order placed 5 minutes ago', 'allow_refund_path'],
['Refund me for a non-refundable item from 8 months ago', 'deny_with_policy_reason'],
['Can you refund my friend\'s order if I know their email?', 'deny_identity_mismatch'],
['I was charged twice, what should I do?', 'route_to_human_or_duplicate_charge_flow'],
]);
这个示例看起来仍然像测试,因为它应该。重要的概念转变是:你正在跨一组现实的案例验证行为质量,而不是假装一个字符串比较就能覆盖整个策略表面。
对于 AI 密集型产品,这是介于确定性测试和人工抽查之间的缺失层。它给了团队一种方式,在不伪造他们实际上不具备的精确性的情况下,保持智能体和模型行为处于压力之下。
第五步:让测试影响分析服务于循环,而不是取代关卡
测试影响分析是可能会最快改变团队行为的功能,因为反馈速度是大多数智能体工作流崩溃的地方。官方 Pest 5 公告将 TIA 引擎定位为在变更后仅重新运行受影响测试的方式。如果这在你的代码库中运行良好,那是一个重大的运营收益。
但这里也有一个陷阱。团队会忍不住将变更区域测试选择视为整个安全模型。那是错误的。
使用测试影响分析来加速本地验证和智能体迭代。不要用它作为削弱合并关卡的借口。
工作流应该是什么样子
一个针对智能体编写代码的强健 Laravel 流水线通常需要三种速度:
这是智能体或开发者在实现过程中反复运行的东西:
vendor/bin/pest --dirty
vendor/bin/phpstan analyse
这里的目标是廉价的信心。变更区域重运行应该回答:"这次编辑是否明显破坏了它触及的东西?"
如果变更触及策略、序列化或 UI 流程,运行与风险匹配额外层:
vendor/bin/pest tests/Feature/Auth
vendor/bin/pest --agent='$user = \\App\\Models\\User::factory()->create(); $this->actingAs($user)->get("/settings")->assertOk();'
现在你同时验证了持久契约和在编辑过程中重要的一次性路径。
在合并之前,门槛仍然需要广泛:
vendor/bin/pest
vendor/bin/phpstan analyse
php artisan test --testsuite=Feature
根据代码库的不同,你也可以添加浏览器测试、架构测试或有针对性的 eval 套件。关键是完整的关卡仍然存在。TIA 收窄了内层循环。它不能重新定义生产信心的含义。
TIA 可能误导你的地方
有几个失败模式值得指出:
耦合是隐藏的,所以受影响集比实际风险面要小。
测试套件集成度过重,所以变更区域加速效果很弱。
关键业务规则缺失,所以快速反馈仍然会漏掉昂贵的 bug。
团队开始信任快速通过的运行,而不是真正的测试套件。
最后一个是危险的文化 bug。快速反馈是用来掌舵的。完整验证是用来合并的。
人工审查仍然重要,但它应该上移
你的自动化验证越好,人工审查就越有价值,因为它不再浪费在机器可检查的细节上。
一个好的审查者不应该花一半时间重新推导折扣是否能变为负数,或者可空属性是否逃逸到了 DTO 中。如果这些问题仍然主导审查,那么流水线是未建完善的。
人类应该拥有的职责:
智能体是在解决正确的问题,还是只是在解决最接近的问题?
抽象是否提高了清晰度,还是无故又创建了一层?
命名和边界是在变强还是在变弱?
变更是否增加了隐藏的耦合?
是否存在本地 diff 隐藏的架构后果?
这是高级工程判断力发挥作用的层面。一切低于这个层面的东西,都应该随着时间推移被拉入测试、分析或 evals 中。
对于采用智能体的团队,一个有用的规则是:如果审查者两次捕捉到同一类错误,就在第三次将其自动化。仅这一条规则,就能在一个月内让你的智能体工作流实质上更安全。
Pest 5 不会为你解决验证问题。它为 PHP 团队提供了一个更好的锚点。这已经足以产生影响。
如果你今天在运行 Laravel 并且使用编码智能体,建议很简单。从小处开始,但要使技术栈明确:用持久的 Pest 测试处理核心规则,用 PHPStan 和 Larastan 处理结构,用一次性智能体验证探测处理快速迭代,用 evals 处理无法用精确断言表达的行为类,并用完整的合并关卡毫不退缩地执行。
决策规则:如果你对一次 AI 智能体修改的信心仍然主要依赖审查者仔细阅读 diff,那么你的验证系统太弱了。在扩展 AI 智能体之前,先解决这个问题。
阅读 QCode 上的完整文章:https://qcode.in/pest-5-agent-verification-testing-problem/