加速你的 PHPUnit 测试

发布时间 作者

Tips to Speed up Your Phpunit Tests image

拥有一个快速测试套件和拥有一个快速应用程序一样重要。作为一名开发者,快速获得关于代码状态的反馈可以使开发周期更快。这里我们将介绍一些你可以立即实施的技巧,让你的测试运行得更快。

示例测试套件被故意设计为缓慢的,以模拟更广泛的测试,并强调可能的改进。你实际运行的效果可能会有所不同。

ParaTest

这个包 是一个 PHPUnit 扩展,它运行你的测试套件,但它不是像 PHPUnit 一样串行运行每个测试用例(一个接一个),而是利用你的机器的 CPU 内核来并行运行它们。

要开始使用 ParaTest,你需要通过 composer 安装它作为开发依赖项。

composer require --dev brianium/paratest

现在,我们只需要调用 ParaTest,就像调用 PHPUnit 一样。它会自动根据你机器上可用的核心数量来确定要使用的进程数量。

你可以在上面的控制台输出中看到它已经确定要使用五个并行进程来运行测试套件。相比之下,下面是在串行模式下使用 PHPUnit 运行相同的测试套件。

1.49 秒 vs 6.15 秒!

虽然 ParaTest 会自行确定要启动的进程数量,但你可能需要尝试调整这个数字,以找到适合你机器的最佳设置。要指定进程数量,可以使用 —processes 选项。你应该尝试添加和删除进程,因为更多并不总是意味着更快的测试。

./vendor/bin/paratest --processes 6

注意:在使用 ParaTest 与一个正在访问数据库的测试套件之前,你需要考虑你是如何准备数据库的。如果你正在使用 Laravel 的 RefreshDatabase 特性,你将会遇到问题,因为一个测试可能会在另一个测试试图写入数据库时回滚或迁移数据库。相反,通过使用 DatabaseTransactions 特性来跳过持久化数据,它也不会在测试套件运行期间尝试更改数据库结构。

重新运行失败的测试

PHPUnit 有一个方便的功能,它允许你只重新运行之前运行中失败的测试。如果你正在做红绿 TDD 风格的开发,这将加速你的开发周期。让我们通过一个通过所有现有测试的测试套件来看看这个功能。

接下来,你添加一个新的测试,根据红绿重构模型,它如预期的那样失败了

在对你的代码库进行你认为会使这个新测试通过的更改之后,你想要重新运行套件以验证它是否按预期工作。问题是这个测试套件已经需要 1.3 秒才能运行,所以随着我们不断添加更多测试,花在等待验证代码上的时间会增加。

如果我们能够只运行我们试图解决的失败测试,那不是很好吗?幸运的是,PHPUnit v7.3 添加了这样做的能力.

要使其工作,请在你的 phpunit.xml 配置中添加 cacheResult="true"。这告诉 PHPUnit 始终记住哪些测试之前失败了。

<?xml version="1.0" encoding="UTF-8"?>
<phpunit cacheResult="true"
backupGlobals="false"
...>

现在,当我们运行测试套件时,PHPUnit 会记住哪些测试失败了,并使用以下选项,我们可以只重新运行那些失败的测试。

./vendor/bin/phpunit --order-by=defects --stop-on-defect

我们不再需要等待整个套件运行才能看到我们试图解决的唯一测试是否通过了。

同样重要的是将缓存文件 .phpunit.result.cache 添加到你的 .gitignore 中,这样它就不会被提交到你的代码库中。

分组慢速测试

PHPUnit 允许你使用 @group 注解 将测试添加到不同的“组”中。如果你有一堆特别慢的测试,可能最好将它们都添加到同一个组中。

class MyTest extends TestCase
{
public function test_that_is_fast()
{
$this->assertTrue(true);
}
 
/**
* @group slow
*/
public function test_that_is_slow()
{
sleep(10);
 
$this->assertTrue(true);
}
 
/**
* @group slow
*/
public function test_that_is_slow_2_adrians_revenge()
{
sleep(10);
 
$this->assertFalse(false);
}
}

在这个例子中,我们有两个测试需要 10 秒才能运行。我们最不希望的事情是在我们的开发周期中运行这些测试,特别是如果你正在做测试驱动的开发,你需要你的测试套件变得敏捷。

由于这两个较慢的测试都在同一个组中,你现在可以通过使用 PHPUnit 的 --exclude-group 选项将它们从测试运行中排除。

./vendor/bin/phpunit --exclude-group slow

这个命令将运行你所有的测试,除了 slow 组中的那些,这将使你的测试运行得更快。这样分组测试的另一个好处是,你正在记录慢速测试,所以希望你能回来改进它们。

然而,重要的是要有一些检查来确保所有测试,包括慢速测试,在部署到生产环境之前都被运行。一个好的方法是建立一个 CI 管道,它运行你所有的测试。

过滤测试

PHPUnit 有一个 --filter 选项,它接受一个模式来确定运行哪些测试。例如,如果你的所有测试都命名空间化了,你可以通过指定一个命名空间来运行测试的特定子集。以下命令将只运行 Tests\Unit\Models 命名空间中的测试,并排除所有其他测试。

./vendor/bin/phpunit --filter 'Tests\\Unit\\Models'

--filter 选项很灵活,允许通过 methodNameClass::methodName 甚至通过文件路径过滤,例如 /path/to/my/test.php。你应该查看 PHPUnit 文档 来了解这个选项,并查看哪些是可能的。

密码哈希轮数

Laravel 默认使用 bcrypt 密码哈希算法,它设计上是缓慢的,并且对系统资源要求很高。如果你的测试验证用户密码,你可以通过设置算法使用的轮数来缩短测试运行时间,因为轮数越多,运行时间越长。

如果你将你的应用程序与 laravel/laravel 项目 中的最新更改保持同步,你会发现哈希轮数可以通过环境变量进行自定义,并且在 phpunit.xml 文件 中已经设置为 4,这是 bcrypt 允许的最小值。

但是,如果你没有跟上最新的更改,你可以在 CreatesApplication 特性中使用 Hash 门面来设置它。

public function createApplication()
{
$app = require __DIR__.'/../bootstrap/app.php';
 
$app->make(Kernel::class)->bootstrap();
 
// set the bcrypt hashing rounds...
Hash::rounds(4);
 
return $app;
}

你可以在 Taylor 的这条推文中看到一些关于这种更改的相当不错的结果。

更新你的 CreatesApplication 特性,包括这个 Hash::setRounds 调用,然后比较运行测试的速度差异。 pic.twitter.com/WKGUOgyTCI

— Taylor Otwell (@taylorotwell) 2017年12月19日

内存数据库

使用内存中的 SQLite 数据库是另一种方法,可以提高访问数据库的测试的速度。你可以通过将这两个环境键添加到你的 phpunit.xml 配置中来快速开始使用它。

<php>
...
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
</php>

警告:虽然这看起来像是一个简单的胜利,但你应该考虑让数据库与你的生产环境保持一致。如果你在生产环境中使用的是 MySQL,那么你应该意识到使用不同数据库(如 SQLite)进行测试可能会带来潜在的问题。我在 我的特性测试套件设置 中更详细地介绍了这些差异。简而言之,我认为在测试期间与生产环境保持一致比获得微不足道的速度提升更重要。

禁用 Xdebug

如果你没有经常使用 Xdebug,你可能需要考虑在需要它之前禁用它。它会减慢 PHP 执行速度,从而降低你的测试套件速度。如果你每天都使用它进行调试,那么在测试运行时禁用它可能不是一个好选择 - 但在考虑测试套件速度时,这一点需要牢记。

你可以看到,在这个测试套件中,禁用 Xdebug 后,速度有了显著提升。这是启用 Xdebug 运行的套件

以及禁用 Xdebug 的相同测试套件

修复你的缓慢测试

当然,最好的建议是:修复你的缓慢测试!如果你难以查明哪些测试导致你的测试套件运行缓慢,你可能需要查看 PHPUnit Report。它是一个开源工具,允许你通过生成一个云图(如下所示)来可视化你的测试套件性能,其中较大的气泡代表缓慢的测试。这将使你能够找到套件中最慢的测试,并逐步提高它们的性能。

timacdonald photo

专注于 TDD 开发引人入胜且性能良好的 Web 应用程序。专门从事 PHP / Laravel 项目。❤️ 为 Web 构建。

Cube

Laravel 时事通讯

加入 40,000 多名其他开发者,绝不错过新的提示、教程等。

Laravel Forge logo

Laravel Forge

轻松创建和管理您的服务器,并在几秒钟内部署您的 Laravel 应用程序。

Laravel Forge
Tinkerwell logo

Tinkerwell

Laravel 开发人员必备的代码运行器。使用 AI、自动完成和对本地和生产环境的即时反馈进行调试。

Tinkerwell
No Compromises logo

无妥协

Joel 和 Aaron,两位来自 No Compromises 播客的经验丰富的开发者,现在可以为您的 Laravel 项目提供雇佣。⬧ 7500 美元/月的固定费用。⬧ 没有冗长的销售流程。⬧ 没有合同。⬧ 100% 退款保证。

无妥协
Kirschbaum logo

Kirschbaum

提供创新和稳定性,以确保您的 Web 应用程序成功。

Kirschbaum
Shift logo

Shift

运行旧版 Laravel?即时、自动的 Laravel 升级和代码现代化,让您的应用程序保持新鲜。

Shift
Bacancy logo

Bacancy

只需 2500 美元/月,即可为您的项目配备经验丰富的 Laravel 开发人员,拥有 4-6 年的经验。获得 160 小时的专用专业知识和 15 天的无风险试用。立即安排通话!

Bacancy
Lucky Media logo

Lucky Media

现在就来试试 Lucky - Laravel 开发的理想选择,拥有超过 10 年的经验!

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar:Laravel 电子商务

Laravel 的电子商务。一个开源软件包,将现代无头电子商务功能的力量带到 Laravel。

Lunar:Laravel 电子商务
LaraJobs logo

LaraJobs

官方 Laravel 工作板

LaraJobs
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit:Laravel SaaS 启动套件

SaaSykit 是一个 Laravel SaaS 启动套件,包含运行现代 SaaS 所需的所有功能。支付、漂亮的结账、管理面板、用户仪表板、身份验证、现成的组件、统计信息、博客、文档等。

SaaSykit:Laravel SaaS 启动套件
Rector logo

Rector

您无缝升级 Laravel、降低成本和加速创新的合作伙伴,为成功企业服务

Rector
MongoDB logo

MongoDB

通过 MongoDB 和 Laravel 的强大集成来增强您的 PHP 应用程序,使开发人员能够轻松高效地构建应用程序。支持事务性、搜索、分析和移动用例,同时使用熟悉的 Eloquent API。了解 MongoDB 的灵活、现代数据库如何改变您的 Laravel 应用程序。

MongoDB
Maska is a Simple Zero-dependency Input Mask Library image

Maska 是一个简单的零依赖输入掩码库

阅读文章
Add Swagger UI to Your Laravel Application image

将 Swagger UI 添加到您的 Laravel 应用程序

阅读文章
Assert the Exact JSON Structure of a Response in Laravel 11.19 image

在 Laravel 11.19 中断言响应的精确 JSON 结构

阅读文章
Build SSH Apps with PHP and Laravel Prompts image

使用 PHP 和 Laravel 提示构建 SSH 应用程序

阅读文章
Building fast, fuzzy site search with Laravel and Typesense image

使用 Laravel 和 Typesense 构建快速、模糊的网站搜索

阅读文章
Add Comments to your Laravel Application with the Commenter Package image

使用 Commenter 软件包将评论添加到您的 Laravel 应用程序

阅读文章