Skip to content

Latest commit

 

History

History
345 lines (303 loc) · 11.2 KB

File metadata and controls

345 lines (303 loc) · 11.2 KB

运行器 API

::: warning 注意 这是高级 API。如果你只需要运行测试,你可能不需要这个。它主要被库的作者使用。 :::

你可以在你的配置文件中使用 runner 选项指定你的测试运行器的路径。这个文件应该有一个默认的导出,其中包含一个实现这些方法的类:

export interface VitestRunner {
  /**
   * 这是在实际收集和运行测试之前被调用的第一件事情。
   */
  onBeforeCollect?: (paths: string[]) => unknown
  /**
   * 这是在收集测试后、"onBeforeRun" 之前被调用的。
   */
  onCollected?: (files: File[]) => unknown

  /**
   * 当测试运行程序应该取消下一次测试运行时调用。
   * 运行程序应该监听此方法,并在“onBeforeRunSuite”和“onBeforeRunTest”中将测试和套件标记为跳过。
   */
  onCancel?: (reason: CancelReason) => unknown

  /**
   * 在运行单个测试之前调用。此时还没有“result”。
   */
  onBeforeRunTask?: (test: TaskPopulated) => unknown
  /**
   * 这是在实际运行测试函数之前被调用的。
   * 此时已经有了带有 "state" 和 "startTime" 属性的 "result" 对象。
   */
  onBeforeTryTask?: (
    test: TaskPopulated,
    options: { retry: number, repeats: number }
  ) => unknown
  /**
   * 这是在结果和状态都被设置之后被调用的。
   */
  onAfterRunTask?: (test: TaskPopulated) => unknown
  /**
   * 这是在运行测试函数后立即被调用的。此时还没有新的状态。
   * 如果测试函数抛出异常,将不会调用此方法。
   */
  onAfterTryTask?: (
    test: TaskPopulated,
    options: { retry: number, repeats: number }
  ) => unknown

  /**
   * 这是在运行单个测试套件之前被调用的,此时还没有测试结果。
   */
  onBeforeRunSuite?: (suite: Suite) => unknown
  /**
   * 这是在运行单个测试套件之后被调用的,此时已经有了状态和测试结果。
   */
  onAfterRunSuite?: (suite: Suite) => unknown

  /**
   * 如果定义了这个方法,它将会替代 Vitest 常规的测试套件分割和处理方式。
   * 但 "before" 和 "after" 钩子函数仍然会被执行。
   */
  runSuite?: (suite: Suite) => Promise<void>
  /**
   * 如果定义了这个方法,它将会替代 Vitest 常规的测试处理方式。
   * 如果你有自定义的测试函数,这个方法就很有用。
   * 但 "before" 和 "after" 钩子函数仍然会被执行。
   */
  runTask?: (test: TaskPopulated) => Promise<void>

  /**
   * 当一个任务被更新时被调用。与报告器中的 "onTaskUpdate" 方法相同。
   * 但该方法在同一个线程中运行,与测试运行在同一个线程中。
   */
  onTaskUpdate?: (task: [string, TaskResult | undefined, TaskMeta | undefined][]) => Promise<void>

  /**
   * 这是在运行收集的所有测试之前被调用的。
   */
  onBeforeRunFiles?: (files: File[]) => unknown
  /**
   * 这是在运行收集的所有测试后立即被调用的。
   */
  onAfterRunFiles?: (files: File[]) => unknown
  /**
   * 当定义了测试的新上下文时调用。如果你想要向上下文中添加自定义属性,这将非常有用。
   * 如果你只是想通过运行器定义自定义上下文,建议在 `setupFiles` 中使用 `beforeAll`。
   */
  extendTaskContext?: (context: TestContext) => TestContext
  /**
   * 在导入某些文件时调用。可以在两种情况下调用:收集测试和导入设置文件。
   */
  importFile: (filepath: string, source: VitestRunnerImportSource) => unknown
  /**
   * 当运行器尝试获取值时调用的函数,此时 `test.extend` 是与 `{ injected: true }` 一起使用的。
   */
  injectValue?: (key: string) => unknown
  /**
   * 公开可用的配置。
   */
  config: VitestRunnerConfig
  /**
   * 当前池的名称。可能会影响服务器端如何推断堆栈跟踪。
   */
  pool?: string
}

在初始化此类时,Vitest 会传递 Vitest 配置,你应该将其作为 config 属性暴露出来:

import type { RunnerTestFile } from 'vitest'
import type { VitestRunner, VitestRunnerConfig } from 'vitest/suite'
import { VitestTestRunner } from 'vitest/runners'

class CustomRunner extends VitestTestRunner implements VitestRunner {
  public config: VitestRunnerConfig

  constructor(config: VitestRunnerConfig) {
    this.config = config
  }

  onAfterRunFiles(files: RunnerTestFile[]) {
    console.log('finished running', files)
  }
}

export default CustomRunner

::: warning Vitest 还会将 ViteNodeRunner 的实例作为 __vitest_executor 属性注入。你可以使用它来处理 importFile 方法中的文件(这是 TestRunnerBenchmarkRunner 的默认行为)。

ViteNodeRunner 暴露了 executeId 方法,该方法用于在友好的 Vite 环境中导入测试文件。这意味着它会在运行时解析导入并转换文件内容,以便 Node 能够理解。

export default class Runner {
  async importFile(filepath: string) {
    await this.__vitest_executor.executeId(filepath)
  }
}

:::

::: warning 如果你没有自定义运行器或没有定义 runTest 方法,Vitest 将尝试自动检索任务。如果你没有使用 setFn 添加函数,这将会失败。 :::

::: tip 快照支持和其他功能是依赖于测试运行器的。如果你想保留这些功能,可以从 vitest/runners 导入 VitestTestRunner 并将你的测试运行器继承该类。它还暴露了 BenchmarkNodeRunner,如果你想扩展基准测试功能的话也可以继承它。 :::

你的任务函数

::: warning “Runner Tasks API” 是实验性的,主要应在测试运行时使用。Vitest 还暴露了 “Reported Tasks API”,在主线程中工作时(例如在报告器内部)应优先使用。

团队目前正在讨论未来是否应将“Runner Tasks”替换为“Reported Tasks”。 :::

套件和测试在内部被称为 tasks。Vitest 运行器在收集任何测试之前会启动一个 File 任务——这是 Suite 的超集,并带有几个附加属性。它作为 file 属性在每个任务(包括 File)上都可用。

interface File extends Suite {
  /**
   * 文件所属的池的名称。
   * @default 'forks'
   */
  pool?: string
  /**
   * 文件的 UNIX 格式路径。
   */
  filepath: string
  /**
<<<<<<< HEAD
   * 文件所属的工作区项目的名称。
=======
   * The name of the test project the file belongs to.
>>>>>>> 7eae51ccf951498de39ec8f8bc8ef2202e8cbe84
   */
  projectName: string | undefined
  /**
   * 收集文件中所有测试所花费的时间。
   * 这个时间还包括导入所有文件依赖。
   */
  collectDuration?: number
  /**
   * 导入设置文件所花费的时间。
   */
  setupDuration?: number
}

每个套件都有一个在收集阶段填充的 tasks 属性。从上到下遍历任务树时,这一属性非常有用。

interface Suite extends TaskBase {
  type: 'suite'
  /**
   * File task. It's the root task of the file.
   */
  file: File
  /**
   * An array of tasks that are part of the suite.
   */
  tasks: Task[]
}

每个任务都有一个引用其所在套件的 suite 属性。如果 testdescribe 在顶级被初始化,它们将不会有 suite 属性(它 不会 等于 file!)。File 也永远不会有一个 suite 属性。从下往上遍历任务时,这一属性非常有用。

interface Test<ExtraContext = object> extends TaskBase {
  type: 'test'
  /**
   * 将传递给测试函数的测试上下文。
   */
  context: TestContext & ExtraContext
  /**
   * 文件任务。它是文件的根任务。
   */
  file: File
  /**
<<<<<<< HEAD
   * 任务是否通过调用 `t.skip()` 被跳过。
=======
   * Whether the task was skipped by calling `context.skip()`.
>>>>>>> 7eae51ccf951498de39ec8f8bc8ef2202e8cbe84
   */
  pending?: boolean
  /**
   * 任务失败时是否应视为成功。如果任务失败,它将被标记为通过。
   */
  fails?: boolean
  /**
   * 存储承诺(来自异步期望)以在完成测试前等待它们。
   */
  promises?: Promise<any>[]
}

每个任务都可以有一个 result 字段。只有当在套件回调或 beforeAll/afterAll 回调中抛出错误,阻止了测试的收集时,套件才会有这个字段。测试在它们的回调被调用后总是有这个字段——stateerrors 字段根据结果的存在与否而存在。如果在 beforeEachafterEach 回调中抛出了错误,抛出的错误将出现在 task.result.errors 中。

export interface TaskResult {
  /**
   * 任务的状态。在收集期间继承 `task.mode`。
   * 当任务完成时,其状态将变为 `pass` 或 `fail`。
   * - **pass**: 任务成功运行
   * - **fail**: 任务失败
   */
  state: TaskState
  /**
   * 在任务执行期间发生的错误。可能存在多个错误。
   * 如果 `expect.soft()` 多次失败。
   */
  errors?: ErrorWithDiff[]
  /**
   * 任务运行所花费的时间(以毫秒为单位)。
   */
  duration?: number
  /**
   * 任务开始运行的时间(以毫秒为单位)。
   */
  startTime?: number
  /**
   * 任务完成后堆的大小(以bytes为单位)。
   * 仅在设置了 `logHeapUsage` 选项且 `process.memoryUsage` 已定义时可用。
   */
  heap?: number
  /**
   * 与该任务相关的钩子状态。在报告期间非常有用。
   */
  hooks?: Partial<Record<'afterAll' | 'beforeAll' | 'beforeEach' | 'afterEach', TaskState>>
  /**
   * 任务重试的次数。只有在任务失败且设置了 `retry` 选项时才会进行重试。
   */
  retryCount?: number
  /**
   * 任务重复的次数。只有在设置了 `repeats` 选项时才会重复任务。此数字也包括 `retryCount`。
   */
  repeatCount?: number
}

你的任务函数

Vitest 提供了 createTaskCollector 工具来创建您自己的 test 方法。它的行为与测试相同,但在收集期间会调用自定义方法。

任务是套件的一部分对象。它会通过 suite.task 方法自动添加到当前套件中:

import { createTaskCollector, getCurrentSuite } from 'vitest/suite'

export { afterAll, beforeAll, describe } from 'vitest'

// 当 Vitest 收集任务时,将调用此函数
// createTaskCollector 只提供了所有的 "todo"/"each"/... 支持,你不必使用它
// 要支持自定义任务,你只需要调用 "getCurrentSuite().task()"
export const myCustomTask = createTaskCollector(function (name, fn, timeout) {
  getCurrentSuite().task(name, {
    ...this, // so "todo"/"skip" is tracked correctly
    meta: {
      customPropertyToDifferentiateTask: true,
    },
    handler: fn,
    timeout,
  })
})
import {
  afterAll,
  beforeAll,
  describe,
  myCustomTask
} from './custom.js'
import { gardener } from './gardener.js'

describe('take care of the garden', () => {
  beforeAll(() => {
    gardener.putWorkingClothes()
  })

  myCustomTask('weed the grass', () => {
    gardener.weedTheGrass()
  })
  myCustomTask.todo('mow the lawn', () => {
    gardener.mowerTheLawn()
  })
  myCustomTask('water flowers', () => {
    gardener.waterFlowers()
  })

  afterAll(() => {
    gardener.goHome()
  })
})
vitest ./garden/tasks.test.js