Testo:一款独立的 PHP 测试与性能基准测试框架
全新 Testo 框架现已开放公测。它是一个零依赖实现——完全不依赖 PHPUnit——支持 PHP 8.2+、插件机制、内联测试及性能基准测试。专为中高级 PHP 开发者设计,Testo 始终坚持灵活性、严格类型约束与极简样板代码。
与 PHPUnit 及其他方案的核心差异
Testo 的诞生,旨在解决 PHPUnit 长期存在的核心痛点:架构僵化、频繁破坏性更新,以及在基础 PHP 版本兼容之外缺乏实质性演进。作者曾长期向 PHPUnit 官方问题追踪器提交建议并贡献 PR,但关键改进始终未被接纳。而 PEST 和 Codeception 实质上继承了 PHPUnit 的全部局限,仅以更繁琐的语法加以包装。
Testo 彻底独立自主:
- 完全摒弃
nikic/php-parser,从根源上规避解析器冲突; - 要求 PHP 8.2+ 运行环境;
- 支持 AI 代理通过
llms.txt(一份源自官方文档的结构化规范)自动批量生成测试用例。
模块化插件系统支持按项目定制功能:所有特性均以可选插件形式交付,每个测试套件可自由组合所需能力。例如,#[Retry] 插件仅用两个轻量文件即可赋予测试用例应对偶发失败的韧性。
通过管道(pipeline)、中间件(middleware)与事件(event)机制灵活扩展行为;直接在 src/ 目录源码中使用 #[TestInline] 编写内联测试,并借助属性(attribute)优雅嵌入测试逻辑。
安装与配置
通过 Composer 安装:
composer require --dev testo/testo
在项目根目录创建 testo.php:
<?php
declare(strict_types=1);
use Testo<Application>Config<ApplicationConfig>;
use Testo<Application>Config<SuiteConfig>;
return new ApplicationConfig(
suites: [
new SuiteConfig(
name: '源码',
location: ['src'],
),
new SuiteConfig(
name: '测试',
location: ['tests'],
),
],
);
该文件需返回一个 ApplicationConfig 实例。若缺失此文件,Testo 将自动回退至默认配置(如扫描 tests/ 目录)。默认预置两套测试集:源码 用于运行嵌入生产代码中的内联测试与性能基准测试;测试 则承载传统单元测试。
执行测试命令为 ./vendor/bin/testo;亦可安装官方 PHPStorm 插件,实现无缝 IDE 集成。
单元测试示例
无需继承任何基类。只需为方法添加 #[Test] 属性:
final class OrderTest
{
#[Test]
public function 计算订单总额(): void
{
$order = new Order();
$order->addItem('图书', price: 15.0, quantity: 2);
$order->addItem('钢笔', price: 3.0, quantity: 5);
Assert::same($order->total(), 45.0);
}
#[Test]
#[DataSet([100.0, 10, 90.0], '享 10% 折扣')]
#[DataSet([100.0, 0, 100.0], '无折扣')]
#[DataSet([0.0, 50, 0.0], '价格为零')]
public function 应用折扣(float $price, int $percent, float $expected): void
{
$result = Order::applyDiscount($price, $percent);
Assert::same($result, $expected);
}
#[Test]
#[ExpectException(InsufficientFundsException::class)]
public function 不允许透支(): never
{
new Account(balance: 100)->withdraw(200);
}
}
对整个类添加 #[Test] 属性后,其所有返回 void 或 never 的方法将自动视为测试用例。独立函数(非类内定义)则无需生命周期属性即可直接运行。
断言采用直观顺序:实际值在前,期望值在后。所有断言均支持链式调用且具备类型安全:
Assert::string($email)->contains('@');
Assert::int($age)->greaterThan(0)->lessThan(150);
Assert::array($items)
->hasKeys('id', 'name')
->isList()
->notEmpty();
Assert::json($response->body())
->isObject()
->hasKeys('data', 'meta');
内联测试与性能基准测试
在 src/ 目录中使用 #[TestInline],可直接验证私有或内部方法:
// src/Money.php
final class Money
{
#[TestInline(['price' => 100.0, 'discount' => 0.1, 'tax' => 0.2], 108.0)]
#[TestInline(['price' => 50.0, 'discount' => 0.0, 'tax' => 0.1], 55.0)]
private static function 计算最终价格(
float $price,
float $discount,
float $tax,
): float {
return $price * (1 - $discount) * (1 + $tax);
}
}
使用 #[Bench] 进行性能基准测试:
#[Bench(
callables: [
'multiply' => 'viaMultiply',
'shift' => 'viaShift',
],
arguments: [1, 5_000],
calls: 2_000_000,
)]
function viaDivision(int $a, int $b): int
{
$d = $b - $a + 1;
return (int) (($d - 1) * $d / 2) + $a * $d;
}
function viaMultiply(int $a, int $b): int
{
$d = $b - $a + 1;
return (int) (($d - 1) * $d * 0.5) + $a * $d;
}
function viaShift(int $a, int $b): int
{
$d = $b - $a + 1;
return ((($d - 1) * $d) >> 1) + $a * $d;
}
典型输出:
+---+----------+-------+---------+------------------+--------+
| # | 名称 | 迭代数 | 调用次数 | 平均耗时 | 标准差 |
+---+----------+-------+---------+------------------+--------+
| 2 | 当前实现 | 10 | 2000000 | 75.890µs | ±0.79% |
| 3 | 乘法运算 | 10 | 2000000 | 78.821µs (+3.9%) | ±0.47% |
| 1 | 位移运算 | 10 | 2000000 | 70.559µs (-7.0%) | ±0.70% |
+---+----------+-------+---------+------------------+--------+
为何值得关注
- 真正意义上的独立框架——零 PHPUnit 依赖,通过可组合插件实现无限扩展;
- 内联测试(
#[TestInline])与性能基准测试(#[Bench])直接与生产代码共存于src/目录,保障逻辑与验证同步演进; - 类型安全、流畅的断言链式调用——告别样板代码、继承束缚与魔法全局变量;
- 配置采用纯 PHP 文件,天然支持多测试套件复杂项目;
- 官方 PHPStorm 插件与 CLI 工具开箱即用,深度融入日常开发流程。
— Editorial Team
暂无评论。