具备高效的测试一如编写高效的应用一样重要. 作为开发者来说, 迅速得知你刚编写的代码是否能够正常运行, 能够让开发效率大大提升. 接下来我们将会介绍一些可以快速实现的小技巧, 让你的代码测试变得更快.
该示例测试套件有意地模拟更广泛的测试集合, 并突出改进的可行性. 真实情况下, 效率的提升可能有所差异.
ParaTest
这个包 https://github.com/paratestphp/paratest 是一个用来运行你的测试套件的 PHPUnit 扩展. 和 PHPUnit 不一样的是它可以利用你的多核 CPU 来并行的运行测试用例.
你可以通过 Composer 来将它作为一个开发依赖安装以后开始使用 ParaTest .
Composer require --dev brianium/paratest
现在我们就可以像调用 PHPUnit 一样来调用 ParaTest 了. 它会自动的根据你机器 CPU 核心数来判断要启动多少个进程.
上面, 你可以看到在控制台中输出了运行测试用例启动了 5 个并行的进程. 对比一下, 下面用 PHPUnit 运行了同样的测试用例.
1.49 秒 和 6.15 秒 !
尽管 ParaTest 可以自己确定进程数, 你也可以尝试设置进程数针对你的机器进行优化. 使用 ---processes 选项, 你可以增加或减少进程数, 因为并不是进程数越多测试效果越好.
./vendor/bin/paratest --processes 6
警告: 使用 ParaTest 测试数据库前, 需要考虑如何准备数据. 如果使用 Lavarel 的 RefreshDatabase , 运行测试用例后会回滚或者迁移数据库来写入 . 相反的是, 通过 DatabaseTransactions 跳过数据持久化, 这在运行测试期间不会尝试修改数据.
重试失败的测试
PHPUnit 有个非常方便的功能就是, 允许你重新只运行上次测试中失败的测试.. 如果你正在进行红绿复建风格的 TDD 开发, 它将会加快你的开发周期. 让我们从一个通过所有现存测试的测试套件来了解一下它这个功能.
接下来, 新增一个 red-green-refactor 测试模型的测试用例, 预期失败:
在更改代码库之后你认为新测试会通过, 你想重新运行该测试套件以期能按预期运行. 问题在于这个套件现在已经要花 1.3 秒的时间才能运行, 随着测试代码量的增加, 所需严重等待的时间也随之增加.
如果我们只能运行失败的测试, 那不是很好? 非常幸运的是 PHPUnit v7.3 添加了这样做的能力.
为了实现这个功能, 请将 cacheResult ="true" 添加到 phpunit.xml 配置中. 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 选项是灵活的, 允许通过 methodName,Class::methodName 进行过滤, 甚至可以通过带有 /path/to/my/test.PHP 的文件路径进行过滤. 您应该查看此选项的 PHPUnit docs 并查看更多的内容.
密码哈希次数
Laravel 默认使用 bcrypt 密码哈希算法, 这种设计在系统资源上缓慢且昂贵. 如果您的测试是验证用户密码, 可以通过设置算法使用的次数来减少测试运行的时间, 因为它执行的次数越多, 所需的时间就越长.
如果你的应用程序与 Laravel/laravelproject https://github.com/laravel/laravel 中的最新更改保持同步, 你会发现哈希次数的数量可以使用环境变量进行自定义. bcrypt 允许的最小次数已经设置为 4, 在 phpunit.xml file.
但是, 如果您没有同步最新的更新, 可以使用 Hash 门面在 CreatesApplication trait 中设置它.
- public function createApplication()
- {
- $App = require __DIR__.'/../bootstrap/app.php';
- $App->make(Kernel::class)->Bootstrap();
- // 设置 bcrypt 哈希次数...
- Hash::rounds(4);
- return $App;
- }
内存数据库
利用内存数据库 SQLite , 是另一种加速测试的方式. 你可以通过在 phpunit.xml 配置文件里添加两个环境字段, 来迅速开启它.
- <PHP>
- ...
- <env name="DB_CONNECTION" value="sqlite"/>
- <env name="DB_DATABASE" value=":memory:"/>
- </PHP>
说明: 尽管这样看上去很容易, 你应该考虑生产环境数据库一致性问题. 如果你在生产环境使用了 MySQL 数据库, 你应该警惕引入不同数据库所带来的测试上的不同, 比如 SQLite. 我在这篇文章 my feature test suite setup 里描述了很多细节上的不同点. 我认为相比通过提升一点速度带来的好处, 保持生产环境一致更重要.
禁用 Xdebug
如果你平时用不到 Xdebug 的话, 可以禁用掉它, 因为它会降低 PHP 执行速度, 导致测试用例运行缓慢. 如果你日常使用它来调试的话, 为了执行测试而禁用它可能不是一个好的选择 -- 但你始终要知道这一点当你关注测试用例执行速度时.
你可以在下面这个测试用例看到, 一旦我们禁用了 Xdebug, 执行速度将会有极大的提高. 下面是这个测试用例在 Xdebug 启用时的执行情况:
以及同样的测试用例在 Xdebug 禁用时的执行情况:
修复测试速度过慢
当然我们最希望看到的段落是是: 修复测试速度过慢! 如果您正在努力确定哪些测试导致测试单元变慢时, 您可能需要查看 PHPUnit Report https://marmelab.com/phpunit-d3-report/ . 它是一个开源工具, 允许您通过生成如下所示的云可视化您的测试单元的性能, 其中较大的气泡代表慢速测试. 这将使您能够在单元中找到最慢的测试并逐步提高其性能.
来源: https://juejin.im/post/5c45264bf265da614933ebc4