Vitest

Vitest 是来自 Vite 生态系统的测试运行器。将其与 Turborepo 集成将带来巨大的速度提升。

Vitest 文档 展示了如何创建一个 “Vitest 工作区”,该工作区从一个根命令运行 monorepo 中的所有测试,从而实现诸如开箱即用的合并覆盖率报告之类的行为。此功能不遵循现代 monorepo 的最佳实践,因为它旨在与 Jest 兼容(Jest 的工作区功能是在包管理器工作区之前构建的)。

因此,您有两个选择,每个选择都有其自身的权衡

利用 Turborepo 进行缓存

为了提高缓存命中率并仅运行具有更改的测试,您可以选择为每个包配置任务,将 Vitest 命令拆分为每个包中单独的、可缓存的脚本。这种速度的权衡是您需要自己创建合并的覆盖率报告。

有关完整示例,请运行 npx create-turbo@latest --example with-vitest访问示例的源代码

设置

假设我们有一个简单的包管理器工作区,如下所示

package.json
package.json

apps/webpackages/ui 都有自己的测试套件,vitest 安装在使用了它们的包中。它们的 package.json 文件包含一个运行 Vitest 的 test 脚本

./apps/web/package.json
{
  "scripts": {
    "test": "vitest run"
  },
  "devDependencies": {
    "vitest": "latest"
  }
}

在根目录 turbo.json 中,创建一个 test 任务

Turborepo logo
./turbo.json
{
  "tasks": {
    "test": {
      "dependsOn": ["transit"]
    },
    "transit": {
      "dependsOn": ["^transit"]
    }
  }
}

现在,turbo run test 可以并行化和缓存来自每个包的所有测试套件,仅测试已更改的代码。

在监听模式下运行测试

当您在 CI 中运行测试套件时,它会记录结果并在完成后最终退出。这意味着您可以使用 Turborepo 缓存它。但是,当您在开发期间使用 Vitest 的监听模式运行测试时,该进程永远不会退出。这使得监听任务更像是一个长时间运行的开发任务

由于这种差异,我们建议指定两个单独的 Turborepo 任务:一个用于运行您的测试,另一个用于在监听模式下运行它们。

下面的策略创建了两个任务,一个用于本地开发,另一个用于 CI。您可以选择将 test 任务用于本地开发,并创建一些 test:ci 任务来代替。

例如,在每个工作区的 package.json 文件中

./apps/web/package.json
{
  "scripts": {
    "test": "vitest run",
    "test:watch": "vitest --watch"
  }
}

并且,在根目录 turbo.json

Turborepo logo
./turbo.json
{
  "tasks": {
    "test": {
      "dependsOn": ["^test"]
    },
    "test:watch": {
      "cache": false,
      "persistent": true
    }
  }
}

您现在可以使用全局 turbo 作为 turbo run test:watch 或从根目录 package.json 中的脚本运行您的任务

终端
turbo run test
 
turbo run test:watch

创建合并的覆盖率报告

Vitest 的工作区功能 创建了一个开箱即用的覆盖率报告,该报告合并了所有包的测试覆盖率报告。但是,按照 Turborepo 策略,您将不得不自己合并覆盖率报告。

with-vitest 示例 显示了一个完整的示例,您可以根据自己的需要进行调整。您可以使用 npx create-turbo@latest --example with-vitest 快速开始使用它。

要做到这一点,您将遵循以下几个一般步骤

  1. 运行 turbo run test 以创建覆盖率报告。
  2. 使用 nyc merge 合并覆盖率报告。
  3. 使用 nyc report 创建报告。

要完成的 Turborepo 任务将如下所示

Turborepo logo
./turbo.json
{
  "tasks": {
    "test": {
      "dependsOn": ["^test", "@repo/vitest-config#build"],
      "outputs": ["coverage.json"]
    }
    "merge-json-reports": {
      "inputs": ["coverage/raw/**"],
      "outputs": ["coverage/merged/**"]
    },
    "report": {
      "dependsOn": ["merge-json-reports"],
      "inputs": ["coverage/merge"],
      "outputs": ["coverage/report/**"]
    },
  }
}

完成这些设置后,运行 turbo test && turbo report 以创建合并的覆盖率报告。

with-vitest 示例 显示了一个完整的示例,您可以根据自己的需要进行调整。您可以使用 npx create-turbo@latest --example with-vitest 快速开始使用它。

使用 Vitest 的工作区功能

Vitest 工作区功能不遵循与包管理器工作区相同的模型。相反,它使用一个根脚本,然后延伸到仓库中的每个包来处理该包中的测试。

在这种模型中,从现代 JavaScript 生态系统的角度来看,没有包边界。这意味着您不能依赖 Turborepo 的缓存,因为 Turborepo 依赖于这些包边界。

因此,如果您想使用 Turborepo 运行测试,则需要使用根任务。一旦您配置了Vitest 工作区,请为 Turborepo 创建根任务

Turborepo logo
./turbo.json
{
  "tasks": {
    "//#test": {
      "outputs": ["coverage/**"]
    },
    "//#test:watch": {
      "cache": false,
      "persistent": true
    }
  }
}

值得注意的是,根任务的文件输入默认包含所有包,因此任何包中的任何更改都将导致缓存未命中。 虽然这确实简化了创建合并覆盖率报告的配置,但您将错过缓存重复工作的机会。

使用混合方法

您可以通过实施混合解决方案来结合两种方法的优点。这种方法统一了使用 Vitest 工作区方法的本地开发,同时在 CI 中保留了 Turborepo 的缓存。这样做需要在仓库中进行稍多的配置和混合任务运行模型之间进行权衡。

./vitest.workspace.ts
import { defineWorkspace } from 'vitest/config';
 
export default defineWorkspace(['packages/*']);

在这种设置中,您的包维护其各自的 Vitest 配置和脚本

./packages/ui/package.json
{
  "scripts": {
    "test": "vitest run",
    "test:watch": "vitest --watch"
  }
}

而您的根目录 package.json 包含用于全局运行测试的脚本

./package.json
{
  "scripts": {
    "test:workspace": "turbo run test",
    "test:workspace:watch": "vitest --watch"
  }
}

此配置允许开发人员在根目录运行 pnpm test:workspace:watch 以获得无缝的本地开发体验,而 CI 继续使用 turbo run test 以利用包级别的缓存。您仍然需要按照上一节中的描述手动处理合并的覆盖率报告