2024年,重新探索前端单元测试之路,从入门到精通02-Vitest

# 2024年,重新探索前端单元测试之路,从入门到精通01-Vitest

十一、实现一个自己的 mini-test-runner

1、目标

  • 通过自己实现一个 mini-test-runner 来理解测试框架的 api 使用

2、实现

  • 首先模拟vitest的方法,例如test/it,describe,expect,生命周期函数等
js 复制代码
// 1.spec.js
import { test, run,describe } from "./core.js"
​
describe("", () => {
  test("first test case", () => {
    console.log("1. first test case")
  })
​
  test.only("second test case", () => {
    console.log("2. second test case")
  })
})
​
run()
js 复制代码
// core.js
let testCallbacks = []
let onlyCallbacks = []
let describeCallbacks = []
let beforeAllCallbacks = []
let beforeEachCallbacks = []
let afterEachCallbacks = []
let afterAllCallbacks = []
export function describe(name, callback) {
  describeCallbacks.push({ name, callback })
  callback()
}
export function test(name, callback) {
  testCallbacks.push({ name, callback })
}
test.only = function (name, callback) {
  onlyCallbacks.push({ name, callback })
}
export const it = test
​
export function expect(pre) {
  return {
    toBe: cur => {
      if (pre === cur) {
        console.log("通过")
      } else {
        throw new Error(`不通过,pre: ${pre} 不等于 cur: ${cur}`)
      }
    },
    toEqual: cur => {
      if (typeof cur === "object") {
        throw new Error(`不通过,类型只能为object`)
      }
      if (pre === cur) {
        console.log("通过")
      } else {
        throw new Error(`不通过,pre: ${pre} 不等于 cur: ${cur}`)
      }
    },
  }
}
​
export function beforeAll(fn){
  beforeAllCallbacks.push(fn)
}
export function beforeEach(fn){
  beforeEachCallbacks.push(fn)
}
export function AfterEach(fn){
  afterEachCallbacks.push(fn)
}
export function AfterAll(fn){
  afterAllCallbacks.push(fn)
}
export function run() {
  const tests = onlyCallbacks.length > 0 ? onlyCallbacks : testCallbacks
  for (const { name, callback } of tests) {
    try {
      callback()
      console.log(`ok: ${name}`)
    } catch (error) {
      console.log(`error: ${name}`)
    }
  }
}

主要的执行逻辑在run方法,通过收集test等内部的回调去执行,并且在执行test.only的时候需要去判断一下

js 复制代码
 const tests = onlyCallbacks.length > 0 ? onlyCallbacks : testCallbacks

这样就可以只执行only的回调了!!!

接下来我们继续补充生命周期的执行方法,其实就是改造run方法

js 复制代码
export function run() {
  // 执行总的beforeAll回调
  beforeAllCallbacks.forEach(fn => fn())
​
  const tests = onlyCallbacks.length > 0 ? onlyCallbacks : testCallbacks
  for (const { name, callback } of tests) {
    // 执行beforeEach的回调
    beforeEachCallbacks.forEach(fn => fn())
​
    try {
      callback()
      console.log(`ok: ${name}`)
    } catch (error) {
      console.log(`error: ${name}`)
    }
    // 执行afterEach的回调
    afterEachCallbacks.forEach(fn => fn())
  }
  // 执行总的afterAll回调
  afterAllCallbacks.forEach(fn => fn())
}

如此一来,我们对于基本的vitestapi的功能就已经完成,接下来我们继续完善,我们写一个插件,让它能够自动运行测试代码,而不是我们手动调用run方法

3、实现自动运行测试代码

js 复制代码
import fs from "fs/promises"
import { glob } from "glob" // 一个匹配文件名的插件
​
// 查找所有以 `.spec.js` 结尾的测试文件
const testFiles = glob.sync("**/*.spec.js", { ignore: "node_modules/**" })
​
console.log("testFiles", testFiles)

运行代码返回结果如下

使用fs模块对文件进行处理

js 复制代码
import fs from "fs/promises"
import { glob } from "glob"
​
// 查找所有以 `.spec.js` 结尾的测试文件
const testFiles = glob.sync("**/*.spec.js", { ignore: "node_modules/**" })
​
// 运行所有测试文件
for (const testFile of testFiles) {
  const fileContent = await fs.readFile(testFile, "utf-8")
  console.log('',fileContent);
}

运行代码返回结果如下

接下来我们需要自动去运行代码,我们把run方法去掉,得需要程序自动去运行

并且我们不只有一个测试文件,所以需要对所有测试文件的内容进行打包合并到一个文件内去运行,这里我使用edbulid

js 复制代码
import fs from "fs/promises"
import { glob } from "glob"
import { build } from "esbuild"
// 查找所有以 `.spec.js` 结尾的测试文件
const testFiles = glob.sync("**/*.spec.js", { ignore: "node_modules/**" })
​
// 运行所有测试文件
for (const testFile of testFiles) {
  const fileContent = await fs.readFile(testFile, "utf-8")
  await runModule(fileContent)
}
​
async function runModule(fileContent) {
  try {
    const result = await build({
      stdin: {
        contents: fileContent,
        resolveDir: process.cwd(),
      },
      write: false,
      bundle: true,
      target: "esnext",
    })
    const transformedCode = result.outputFiles[0].text
​
    console.log("result", transformedCode)
  } catch (error) {
    console.error("Error executing module:", error)
  }
}

运行后看见确实合并在一起了

4、最终代码

js 复制代码
import fs from "fs/promises"
import { glob } from "glob"
import { build } from "esbuild"
// 查找所有以 `.spec.js` 结尾的测试文件
const testFiles = glob.sync("**/*.spec.js", { ignore: "node_modules/**" })
​
// 运行所有测试文件
for (const testFile of testFiles) {
  const fileContent = await fs.readFile(testFile, "utf-8")
  await runModule(fileContent + "import { run } from './core.js'; run()")
}
​
async function runModule(fileContent) {
  try {
    // 转换代码为 CommonJS 格式并捆绑依赖
    const result = await build({
      stdin: {
        contents: fileContent,
        resolveDir: process.cwd(),
      },
      write: false,
      bundle: true,
      target: "esnext",
    })
    // 获取转换后的代码
    const transformedCode = result.outputFiles[0].text
    // 执行转换后的代码
    const runCode = new Function(transformedCode)
    runCode()
  } catch (error) {
    console.error("Error executing module:", error)
  }
}
​

最后我们使用暴力的方式,去运行测试代码

所有文件都测试完毕

以下内容等待更新...

相关推荐
北京_宏哥2 天前
《最新出炉》系列入门篇-Python+Playwright自动化测试-50-滚动条操作
python·前端框架·测试
kida_yuan4 天前
【从零开始】6. RAG 应用性能压测工具(番外篇)
后端·llm·测试
孤蓬&听雨9 天前
Kafka自动生产消息软件(自动化测试Kafka)
分布式·kafka·自动化·测试·生产者
帅得不敢出门12 天前
Python+Appium+Pytest+Allure自动化测试框架-安装篇
python·appium·自动化·pytest·测试·allure
陈明勇13 天前
自动化测试在 Go 开源库中的应用与实践
后端·go·测试
帅得不敢出门14 天前
Python+Appium+Pytest+Allure自动化测试框架-代码篇
python·appium·自动化·pytest·测试·allure
Dylanioucn14 天前
《解锁 TDD 魔法:高效软件开发的利器》
后端·功能测试·测试·测试驱动开发·tdd
北京_宏哥15 天前
《最新出炉》系列入门篇-Python+Playwright自动化测试-41-录制视频
前端·python·测试
努力的小雨16 天前
新手入门Java自动化测试的利器:Selenium WebDriver
后端·测试
画江湖Test21 天前
pytest 单元框架里,前置条件
python·自动化·pytest·测试·1024程序员节