Vitest 源码解读(二)———— suite 和 test 实现

如果想了解以及执行 suite 和 test 究竟会发生什么,我们就需要先来看一下,suite 和 test 是如何实现的。

describe 是 suite 的别名,我们来看下 suite 实现:

export const suite = createSuite()
export const defaultSuite = suite('')

function createSuite(name, factory) {
  const tasks = [];
  let suite = {
    id: '',
    type: 'suite',
    name,
    mode,
    // 在collect 中赋值
    tasks: [],
  }

  // 实现 test 方法
  const test = function(name, fn, timeout) {
    const test = {
      id: '',
      type: 'test',
      name,
      mode,
      // 在 collect 中赋值
      suite: undefined!,
    }

    setFn(test, fn)
    tasks.push(test)
  }

  // 执行 suite 的 factory 方法,从而收集 test 的 tasks
  async function collect(file) {
    if (factory) {
      await factory(test)
    }

    suite.file = file
    suite.tasks = tasks

    tasks.forEach(task => {
      task.suite = suite
      if (file) {
        task.file = file
      }
    })

    return suite
  }

  const collector = {
    type: 'collector',
    name,
    mode: 'run',
    // test 方法
    test,
    // 执行过的 test 上下文
    tasks,
    // 执行 suite 的 factory 方法并填充上下文
    collect,
    // 清除上下文信息
    clear,
    on: addHook,
  }

  return collector;
}

可以看到,执行 suite/describe 方法,会返回一个 collector 对象,该对象包含 test、tasks 和 collect 方法。那么这些方法都是在什么时机被调用的呢?我们来看一下主流程的函数:

上文提到 entry.js 中的逻辑如下,该函数用于具体执行测试用例:

async function run(files, config) {
  for (const file of files) {
    // 收集测试用例
    const files = await collectTests([file], config)
    // 执行测试用例
    await runSuites(files)
  }
}

我们来看一下 collectTests 如何实现:

async function collectTests(paths, suite) {
  const files = []

  for (const filepath of paths) {
    // 获取相对路径
    await import(filepath)

    // 获取需要执行的 suites
    const collectors = [defaultSuite, ...context.suites]
    const suites: Suite[] = []

    const file: File = {
      filepath,
      suites: [],
    }

    // 执行 suites 的 collect 方法
    for (const c of collectors) {
      context.currentSuite = c
      suites.push(await c.collect(file))
    }

    file.suites = suites

    files.push(file)
  }

  // 返回收集到的上下文信息
  return files;
}

收集完毕后,我们来看一下是如何执行的:

for (const file of files) {
  for (const suite of file.suites) {
    for (const t of suite.tasks)
      await getFn(task)
  }
}  

如何实现链式调用呢, 如:

  • test.skip.concurrent
  • test.only.concurrent

function createChainable(keys, fn) {
    function create(obj) {
      const chain = function (...args) {
        return fn.apply(obj, args)
      }

      for (const key of keys) {
        Object.defineProperty(chain, key, {
          get() {
            return create({...obj, [key]: true})
          }
        })
      }

      return chain
    }

    const chain = create({})
    chain.fn = fn

    return chain
}