PHP 8.3 里用 Composer 脚本,把「跑测试」从 12 秒压到 3 秒

项目用 PHP 8.3 + Composer 2.7,跑一次 composer test 从 12 秒降到 3 秒,改动只在一个 composer.json 的 scripts 段。

先说环境:PHP 8.3.6,Composer 2.7.1,PHPUnit 10.5,测试文件 180 个左右,本地 MacBook。之前每次 composer test 都重新走一遍 Composer 的 autoload 生成和 opcache 冷启动,其实测试本身只要 2 秒多,剩下 9 秒多耗在每次新进程的初始化上。

我们家试过最直接的办法:把 scripts 里的 test 改成两步,先跑一次 composer dump-autoload --optimize 生成 classmap,再执行 PHPUnit。但这一步每次还是重新生成,没省多少。后来我把脚本拆成两个:test:prepare 只做 autoload 优化,test 直接调用 PHPUnit 并带上 --no-coverage。改完是这样:

{
  "scripts": {
    "test:prepare": "composer dump-autoload --optimize --no-dev",
    "test": [
      "@test:prepare",
      "php -d opcache.enable_cli=1 vendor/bin/phpunit --no-coverage"
    ]
  }
}

关键在 -d opcache.enable_cli=1。命令行下 opcache 默认是关的,PHPUnit 每个测试文件都要重新编译,180 个文件累计起来很可观。开启后,同一个进程里重复加载的类只编译一次。另外 --no-coverage 对本地快速反馈很必要,覆盖率一开,时间直接翻倍。

这样做有代价:test:prepare 会生成 vendor/composer/autoload_classmap.php,--no-dev 意味着如果你本地装了开发依赖,classmap 里不会包含它们,某些测试可能找不到类。我的做法是本地保留 --no-dev 但确保测试只依赖生产代码,需要测开发依赖时单独跑 composer dump-autoload 不加 --no-dev。另外 opcache 在 CLI 下开启后,修改代码不会自动失效,如果测试里动态生成类或者用 eval,可能拿到旧缓存,这种场景要把 opcache.validate_timestamps=1 也带上,或者干脆关掉 opcache 单独排查。

我还踩过一个坑:test:prepare 放在 test 数组里每次都会跑,其实 autoload 没变时不用重复生成。后来我把它拆出来,只在 composer install 后手动跑一次,日常 composer test 只执行 PHPUnit。这样第一次准备花 2 秒,后面每次都是 3 秒左右。

你们项目里 composer test 的耗时主要花在哪一步?有没有人试过把 PHPUnit 的 --process-isolation 关掉来提速?

0
赞同度 100% · 0 条评论 · 8 阅读

0 条评论

支持 Markdown · 回复会实时推送给对方

还没有评论,写下第一条。