跳转至

14 代码单元测试:如何轻松地保证自己的代码质量?

你好,我是杨文坚。

回顾前面Vue.js 3.x自研组件库的几节课,我们分别学习了如何开发主题方案、基础组件、动态渲染组件、布局组件和表单组件,这些都是构成基础组件库的主要因素,也是我们后续开发业务组件库和打造一个运营搭建平台的前端“基石”。

要知道,在开发业务组件库和打造运营搭建平台的时候,组件库代码的“稳定性”和“健壮性”是非常重要的。如果基础组件库不稳定,经常出问题,那么基于它构成的业务组件或前端页面就会频繁出Bug。那么,组件库出问题会有哪些原因呢?

一般是组件的“逻辑分支多”和“测试不彻底”。举个例子,假设你开发了一个按钮组件(Button),按钮组件又被对话框组件、表单组件使用。这时候如果你给按钮组件添加一个监听键盘快捷键的功能,开发完成后,经过人工验证保证了按钮组件本身原有功能一切正常,但使用了按钮组件的对话框和表单组件,也能正常使用吗?是不是也得人工验证一遍?如果按钮组件被十多个其它基础组件引用,是不是也得逐个人工验证?

这里的组件设计和内部依赖使用出现了“逻辑分支多”的问题,涉及的逻辑功能都要人工验证,容易导致“测试不彻底”的隐患。随着组件库里的组件积少成多,这类隐患也越来越多,最终可能“量变引起质变”,导致“千里之堤,溃于蚁穴”的生产故障。

那么,有办法打破这一困境吗?答案当然是有的,我们可以使用“单元测试”,通过技术的手段来自动化“测试代码”。

什么是单元测试?

单元测试,英文是Unit Test,也可以称之为“模块测试”,主要是对代码最小单位逐一进行测试验证功能。这里的“代码最小单位”可以是一个函数、一个组件、一个类,甚至是一个变量。只要是能执行功能的代码模块,都可以称之为一个“最小单位”。

单元测试里的“单元”,是代码里可以执行的“单位”,测试就是验证这个最小单位的代码执行完后的结果是否符合预期。

举个例子,如果我们要开发一个数学加减乘除的功能代码,加法函数就是其中一个可执行的最小单位:

// 这是一个加减乘除的函数集合对象
const myMath = {

  // 这里加法函数,可以当做是最小的测试单位
  add(num1, num2) {
    return num1 + num2;
  },

  subtract(num1, num2) {
    return num1 - num2;
  },

  multiply(num1, num2) {
    return num1 * num2;
  },

  divide(num1, num2) {
    return num1 / num2;
  }
};

这时候,要对加法函数这个“单元”进行单元测试,如果测试成功,就输出成功提示,如果测试失败,也就是测试结果不符合预期,就抛出错误(throw Error)。我们可以这么来实现测试代码:

const result = myMath.add(1, 2);
const expect = 3;
if (result === expect) {
  console.log('myMath.add 加法测试成功!');
} else {
  throw Error(
    `myMath.add 加法测试失败,期待结果应该是:${expect},但实际结果为:${result}`
  );
}

上述代码在Node.js环境里测试成功的效果,如下图所示:

图片

这时候,如果我们将 expect 变量修改一下,期待值就不符合预期,触发测试失败,报错效果如下:

图片

不过,上述测试代码使用throw Error,会中断JavaScript的执行流程。如果我们要测试所有方法,并且要收集结果,也要throw出错误,那可以这么实现一个最简单的单元测试管理方法,代码如下所示:

const allUnitTestResults = [];
function unitTest(name, callback) {
  let success = false;
  let error = null;
  try {
    callback();
    success = true;
  } catch (err) {
    error = err;
  }
  allUnitTestResults.push({
    name,
    success,
    error
  });
}

unitTest('加法函数 add', () => {
  const result = myMath.add(1, 2);
  const expect = 3;
  if (result === expect) {
    console.log('myMath.add 加法测试成功!');
  } else {
    throw Error(
      `myMath.add 加法测试失败,期待结果应该是:${expect},但实际结果为:${result}`
    );
  }
});

unitTest('减法函数 subtract', () => {
  const result = myMath.subtract(3, 2);
  const expect = 1;
  if (result === expect) {
    console.log('myMath.add 减法测试成功!');
  } else {
    throw Error(
      `myMath.add 减法测试失败,期待结果应该是:${expect},但实际结果为:${result}`
    );
  }
});

let successCount = 0;
let failCount = 0;
allUnitTestResults.forEach((item) => {
  if (item.success === true) {
    successCount++;
  } else {
    failCount++;
    console.log(item.error);
  }
});

console.log(`总共 ${allUnitTestResults.length}个测试用例`);
console.log(`测试成功个数: ${successCount}`);
console.log(`测试失败个数: ${failCount}`);

测试效果运行如下图所示:
图片

你可以看到,“单元测试”是我人工写JavaScript代码来管理的,那么,能否有对应的JavaScript框架可以来统一管理呢?

答案是肯定的,接下来我们就来看看前端单元测试都要准备什么工具。

前端单元测试需要用什么工具?

我们先来看下,前端单元测试有什么必备的要素。前面的“数学加减乘除”功能代码的测试实现,可以分成三个过程:

  • 第一步:“输入”要执行的单元代码,等带执行完的“输出”结果;
  • 第二步:执行后的“输出”进行“断言”,这里的“断言”就是指结果是否符合预期;
  • 第三步:逐个收集所有单元测试结果,并做最后的统计处理。

知道了前端单元测试的核心过程后,你还需要注意前端代码的运行环境。因为在绝对大多数的前端项目中,JavaScript的运行环境主要有Node.js和浏览器这两种,这两种环境有比较大的API支持差异。例如,在Node.js环境中,没有浏览器环境里的操作DOM的API。

我们现在做的是Vue.js相关的单元测试,Vue.js是支持在浏览器操作DOM的框架,所以我们在选择单元测试工具时候,必须支持浏览器的API。

目前,市面支持测试“断言”或“测试管理”的主流前端JavaScript单元测试工具,有Mocha、Jest和Vitest:

  • Mocha 是面向Node.js环境的JavaScript单元测试,不能直接支持浏览器的API,断言可以使用Node.js自带assert模块或者第三方断言工具,例如Chai;
  • Jest 是同时支持Node.js和在Node.js里模拟浏览器API的测试工具,内部自带测试“断言”和“管理”工具,是React.js官方维护的测试工具。
  • Vitest 跟Jest一样,都能支持Node.js和浏览器API,也自带测试“断言”和“管理”工具,是Vue.js官方维护的测试工具,对Vue.js的支持能力比较友好。

既然Vitest是Vue.js官方支持和维护,那么显而易见,我们选择Vitest是比较有优势的。所以这节课的单元测试,我们都围绕Vue.js官方测试工具Vitest来进行。

那么,如何用Vitest,给Vue.js 3.x组件库做单元测试呢?

如何用Vitest给Vue.js 3.x组件库做单元测试?

使用Vitest来做单元测试,我们首先要做的是环境准备。环境准备主要分成两个步骤,安装相关npm模块依赖和做Vitest的项目配置。

先来看第一步,安装相关npm模块依赖:

# 基于 npm 安装
npm i -D vitest @vue/test-utils @vitejs/plugin-vue @vitejs/plugin-vue-jsx jsdom

# 或者基于 pnpm 来安装
pnpm i -D vitest @vue/test-utils @vitejs/plugin-vue @vitejs/plugin-vue-jsx jsdom

每个npm模块的作用:

  • vitest, 是Vitest测试工具核心模块,提供了单元测试管理和断言等工具;
  • @vue/test-utils,是Vue.js测试工具,辅助处理Vue.js在Node.js环境里操作DOM渲染和DOM事件等操作;
  • @vitejs/plugin-vue,是Vitest的插件,支持直接构建运行Vue.js模板代码;
  • @vitejs/plugin-vue-jsx,是Vitest的插件,支持直接构建运行Vue.js的JSX代码;
  • jsdom,用来在Node.js环境中模拟浏览器的原生API,例如操作DOM的原生API等。

第二步,在安装依赖后,我们就需要做Vitest的项目配置。先在项目根目录创建文件 ./vitest.config.js:

import { defineConfig } from 'vitest/config';
import PluginVue from '@vitejs/plugin-vue';
import PluginJsx from '@vitejs/plugin-vue-jsx';
export default defineConfig({
  // 配置插件,用来在测试过程中编译Vue.js的模板语法和JSX语法
  plugins: [PluginVue(), PluginJsx()],
  // 配置测试环境,支持全局变量和浏览器DOM API
  test: {
    globals: true,
    environment: 'jsdom'
  }
});

然后在根目录的 ./package.json 添加测试脚本:

{
  // ...
  "scripts": {
    // ...
    "test": "vitest",
    "test:update": "vitest --update",
  }
  // ...
}

现在我们可以开始来写一个单元测试,例如新建文件 ./packages/components/__tests__/demo.test.ts,小试一下单元测试::

import { describe, test, expect } from 'vitest';

describe('Demo', () => {
  test('Test case', () => {
    const a = 1;
    const b = 2;
    expect(a + b).toBe(3);
  });
});

执行测试命令 npm run test,vitest会自动识别当前项目中所有 *.test.ts 的测试文件进行执行测试,统计测试结果最后反馈出来,具体效果如下所示:

图片

Vitest项目的单元测试基础配置就弄好了。接下来,我们就要进入今天的重点,也是难点,Vue.js组件的场景。

Vue.js组件的场景,比前面举例的“数学加减乘除”的功能更加复杂,多了DOM渲染、DOM事件操作、请求处理等浏览器里独有的特性。这些特性不是一个简单的“输入待测试代码”和“断言输出结果”的操作就能满足的,那么这些特性难点要怎么进行单元测试呢?

我们先对这些“测试难点”分类,分成不同测试场景类型,然后找到每个场景类型中的一个典型案例,举一反三就能覆盖绝大部分的“测试难点”了。这里,我划分成四种测试场景类型:

  • 渲染测试场景;
  • 行为测试场景;
  • 请求测试场景;
  • 兜底测试场景。

我们逐一看看。

1. 渲染测试场景

渲染测试场景,主要是验证Vue.js组件渲染后的DOM结构是否符合预期,也就是组件在渲染后的HTML结构是否符合预期,一般会直接用“快照比对”的方式进行断言。

渲染场景单元测试的“输入”是组件,输出是“快照”,具体测试操作就是断言“快照”是否符合预期,也就是说,我们需要有个符合预期的“正确快照”进行对比。一般这个“正确快照”是首次测试时候就生成的,修改代码后,再次执行单元测试会跟这个“正确快照”进行对比,而且这个“正确快照”首次生成后是不会自动更新的,如果有需要必须自己手动强行更新。

我拿前几节课的基础组件库的Box组件来做一次快照测试,具体单元测试就在文件 ./packages/components/__tests__/box/snapshot1.test.ts 里,具体测试代码如下:

import { describe, test, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import BoxTest from './index.test.vue';

describe('Box', () => {
  test('snapshot', () => {
    const wrapper = mount(BoxTest);
    expect(wrapper).toMatchSnapshot();
  });
});

测试所用的输入案例代码在文件 ./packages/components/__tests__/box/index.test.vue 里:

<template>
  <Box>
    <div class="hello">Hello World</div>
  </Box>
</template>

<script setup lang="ts">
import { Box } from '../../src';
</script>

执行单元测试后就会自动生成快照文件,会跟单元测试文件名称同名,自动生成在目录 ./packages/components/__tests__/box/__snapshots__ 里。

图片

这时候,我们再修改Box里个别DOM的className,执行默认单元测试操作时就会报错,也就是说,生成的快照与首次的快照不一样就会报错,如下所示:

图片

这个时候,如果你认为DOM内容变更是必须的,那意味着,期待结果的正确快照也要被更新,那你就可以执行这个Vitest命令 vitest --update,在这节课的项目中,我把它封装成了脚本命令 npm run test:update,执行一下就可以更新快照。

看到这儿,你可能会问:总是这么生成快照、对比快照,有需要就要更新快照,这个操作有点繁琐,还有其他的渲染测试方法吗?

答案是有的。这个测试的“输入”是组件,“输出”是快照,“断言”是快照,所以只要从“输入”、“输出”和“断言”这三个因素入手调整就行了。渲染测试,就是要看DOM或者HTML结构是否符合预期,那么我们可以选择一些重要的DOM或HTML标签,作为断言标准。

回到这个Box组件,我们可以选择className作为重要指标,那么这个单元测试的因素就变成了:“输入”是组件,“输出”是DOM结构,“断言”是判断className的DOM是否存在。我将它写在了这节课代码案例的 ./packages/components/__tests__/box/render.test.ts 文件里,具体代码如下所示:

import { describe, test, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import BoxTest from './index.test.vue';

describe('Box', () => {
  test('className', () => {
    const wrapper = mount(BoxTest);
    const boxDOM = wrapper.find('.my-vue-box');
    // 判断className为my-vue-box的DOM是否存在
    expect(boxDOM).toBeTruthy();

    const slotDOM = boxDOM.find('.hello');
    // 判断className为my-vue-box的DOM内部子节点DOM是否存在
    expect(slotDOM).toBeTruthy();
    expect(slotDOM.text()).toBe('Hello World');
  });
});

通过调整后,不需要频繁生成快照和对比快照了。

2. 行为测试场景

行为测试场景,主要是验证Vue.js组件渲染后,在用户行为操作DOM后触发的DOM结构的变化。例如,用户点击了组件的按钮,触发了组件内部其他渲染变化,这个时候“输入”是组件和行为操作,“输出”是变化后的DOM结构,“断言”是判断变化后的DOM结构快照或者DOM变化指标。

我们就怎么简单怎么来,按照DOM变化的指标来做断言测试。这里,我们测试一下Button按钮的点击行为操作是否正常,需要写一个按钮的计数器来作为单元测试验证,待输入的测试代码(在案例 ./packages/components/__tests__/button/index.test.vue 文件中)如下所示:

<template>
  <div class="display-text">当前数值={{ num }}</div>
  <Button class="btn-add" @click="onClick">点击加1</Button>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { Button } from '../../src';
const props = defineProps<{ num: number }>();
const num = ref<number>(props.num);
const onClick = () => {
  num.value++;
};
</script>

具体单元测试代码(在案例 ./packages/components/__tests__/button/index.test.ts 文件中)如下所示:

import { describe, test, expect } from 'vitest';
import { nextTick } from 'vue';
import { mount } from '@vue/test-utils';
import ButtonTest from './index.test.vue';

describe('Button', () => {
  test('click event', async () => {
    // 模拟浏览器渲染组件
    const wrapper = mount(ButtonTest, { props: { num: 123 } });
    // 找到数字文案DOM节点
    const textDOM = wrapper.find('.display-text');
    // 找到按钮DOM节点
    const btnDOM = wrapper.find('.btn-add');
    // 断言验证点击前的文案
    expect(textDOM.text()).toBe('当前数值=123');
    // 触发按钮点击
    btnDOM.trigger('click');
    // 等待DOM变化结束
    await nextTick();
    // 断言验证结果(点击后的文案)
    expect(textDOM.text()).toBe('当前数值=124');
  });
});

代码里,我用前几节课里的Button组件和Input组件,写了一个计数器高阶组件,来实现点击计数效果,验证用户操作Button和Input组件是否正常。用@vue/test-utils来渲染组件和触发组件里的操作事件,就是常见的模拟用户行为的单元测试。

3. 请求测试场景

请求测试场景,比较特殊,主要适用于组件内部有涉及请求数据,例如图片请求的情况。那么在单元测试的流程里,“输入”就是组件和数据请求,“输出”就是数据请求结果,“断言”就是判断请求结果是否符合预期。

那么问题来了,Node.js环境里模拟浏览器操作,请求行为是无法模拟的,所以这时候就需要我们人工来模拟请求操作

举个例子,我这里开发了一个基础组件Image,要写个图片组件的请求图片成功和失败的单元测试:

import { describe, test, expect } from 'vitest';
import { mount, flushPromises } from '@vue/test-utils';
import { Image } from '../../src';

// 模拟重写 img标签,自动触发图片请求操作
window.document.createElement = (function (create) {
  return function () {
    // @ts-ignore
    const element: HTMLElement = create.apply(this, arguments);
    if (element.tagName === 'IMG') {
      // 图片元素渲染后,模拟自动触发图片请求的延时请求操作
      setTimeout(() => {
        const src = element.getAttribute('src');
        if (src?.includes('error.jpg')) {
          // 如果图片链接有带error.jpg,就触发请求错误
          element.dispatchEvent(new CustomEvent('error', { bubbles: true }));
        } else {
          // 其它默认触发请求成功
          element.dispatchEvent(new CustomEvent('load', { bubbles: true }));
        }
      }, 100);
    }
    return element;
  };
})(window.document.createElement);

describe('Image', () => {
  test('load', async () => {
    let resolve: Function;
    let reject: Function;
    new Promise((res, rej) => ((resolve = res), (reject = rej)));
    // 渲染验证图片请求成功
    mount(Image, {
      props: {
        src: './xxx/xxxx.jpg'
      },
      emits: {
        load: (e: Event) => {
          expect(e).toBeTruthy();
          resolve?.();
        },
        error: () => {
          reject?.();
        }
      }
    });
    await flushPromises();
  });

  test('error', async () => {
    let resolve: Function;
    let reject: Function;
    new Promise((res, rej) => ((resolve = res), (reject = rej)));
    // 渲染验证图片请求失败
    mount(Image, {
      props: {
        src: './xxx/error.jpg'
      },
      emits: {
        load: () => {
          reject?.();
        },
        error: (e: Event) => {
          expect(e).toBeTruthy();
          resolve?.();
        }
      }
    });
    await flushPromises();
  });
});

上述测试过程中,你会发现,我是重写模拟了Image的虚拟节点创建操作,模拟触发了图片成功请求以及请求失败的操作。

实际开发中,Ajax请求也可以做类似的模拟,主要是模拟重写HttpRequestXML这个全局对象,一般有现成的npm模块,例如xhr-mock。

4. 兜底测试场景

兜底测试场景,就是要做前面三个场景都无法覆盖全面的活,利用Vitest的模拟浏览器环境,用Vue.js传统渲染方式作为“输入”,“输出”是一张在Node.js环境里模拟浏览器里的页面,“断言”就是判断在这个“模拟页面”上是否存在我们想要的结果指标。

例如我们要测试某个DOM是否存在,就可以这么实现代码:

import { describe, test, expect } from 'vitest';
import { createApp, nextTick } from 'vue';
import AppTest from './app.test.vue';

describe('Button', () => {
  test('click event', async () => {
    // 用jsdom模拟的浏览器API渲染一个根节点DOM div
    const div = document.createElement('div');
    // 将div追加到页面body上
    document.body.appendChild(div);
    // 用常规浏览器里Vue.js创建应用的方式创建应用
    const app = createApp(AppTest);
    // 用常规浏览器里Vue.js挂载应用的方式挂载组件
    app.mount(div);
    // 用jsdom模拟的浏览器API 查找需要断言的DOM
    const textDOM = div.querySelector('.display-text');
    // 用jsdom模拟的浏览器API 查找需要断言的DOM
    const btnDOM = div.querySelector('.btn-add');
    expect(textDOM?.textContent).toBe('当前数值=123');
    btnDOM?.dispatchEvent(new CustomEvent('click', { bubbles: true }));
    await nextTick();
    expect(textDOM?.textContent).toBe('当前数值=124');
  });
});

代码直接利用浏览器的API,在Node.js单元测试环境中直接调用,验证渲染后DOM是否存在。

这四个测试场景类型,已经能覆盖绝对大多数的组件场景了。基于单元测试,每次修改代码后,我们都能用自动单元测试,自动验证所有功能是否正常,不再需要人工形式来测试验证,极大地解放开发者的生产力。

但不知道你有没有发现一个问题,作为一个保护代码功能质量的屏障,我们能用什么来衡量单元测试的质量呢?换句话说,我们能用什么指标来衡量单元测试的测试效果呢?这个指标就是单元测试的“覆盖率”。

单元测试覆盖率

单元测试覆盖率,指的是在被测试的代码中,被执行的代码占所有代码的比例。我们可以通过这个指标,找出哪些代码还没被测试覆盖到,避免出现功能逻辑分支被遗漏的问题。

测试覆盖率一般有四个指标:

  • 状态覆盖率;
  • 代码行数覆盖率;
  • 逻辑分支覆盖率;
  • 方法覆盖率。

Vitest配置覆盖率的方式很简单,只要做以下三个配置步骤就可以了。

第一步,安装覆盖率的统计npm模块,@vitest/coverage-c8 。

第二步,修改配置vitest.config.js,修改结果如下所示:

import { defineConfig } from 'vitest/config';
import PluginVue from '@vitejs/plugin-vue';
import PluginJsx from '@vitejs/plugin-vue-jsx';
export default defineConfig({
  plugins: [PluginVue(), PluginJsx()],
  test: {
    globals: true,
    environment: 'jsdom',
    coverage: {
      // 覆盖率统计工具
      provider: 'c8',
      // 覆盖率的分母,packages/ 目录里
      // 所有src的源文件作为覆盖率统计的分母
      include: ['packages/*/src/**/*'],
      // 全量覆盖率计算
      all: true
    }
  }
});

第三步,配置package.json单元测试执行脚本。在根目录的 ./package.json 添加测试覆盖率脚本:

{
  // ...
  "scripts": {
    // ...
    "coverage": "vitest run --coverage"
  }
  // ...
}

接下来就是执行单元测试操作,执行命令 npm run coverage 后,会自动生成覆盖率统计报告。具体结果如下图所示:

图片

测试覆盖率报告在 coverage/ 目录里,我们可以用浏览器直接打开 coverage/index.html 页。用浏览器访问后如下图所示:

图片

图片

通过测试报告截图和提示,我们就可以根据这个测试覆盖率情况,找到没被覆盖到的代码,补充对应代码逻辑的单元测试。

好,到这里,我们就已经从Vue.js组件单元测试验证功能,再到覆盖率验证测试质量,走了一遍一个企业级Vue.js组件库项目,所需的完整单元测试流程。

但是,单元测试,只是验证代码“输入”后的“输出”是否符合预期,它的上限就是验证核心功能逻辑,所以说,单元测试也存在一定的局限性。

前端的单元测试,只能通过“数据”形式来保证测试结果和测试断言,无法验证最终渲染的视觉效果。而且,目前大多数前端单元测试都是在Node.js环境里进行的,跟实际浏览器还是存在差异。如果要验证最终视觉效果,我们就要用到E2E测试,也就是“End to End”的端对端测试,你可以选择使用 Cypress 这个E2E测试工具。

此外,单元测试还有另一个局限性,在频繁变更功能的需求场景下,每次变更功能,我们都必须重写测试用例,这样时间成本会大大增加。所以,单元测试等这些测试操作,大多数用于比较稳定的代码,例如我们举例的组件库代码。当然了,这个局限性也不仅仅局限于单元测试,E2E测试等测试操作都有。

总之,测试不是万能的。我们目标是追求稳定健壮的代码功能,测试只是达到目的的一个比较高效的方式,并不是一个完美的方式。

总结

通过这节课对前端单元测试的分析,以及基于Vitest来实现Vue.js 3.x组件库的单元测试,相信你已经深刻理解了前端单元测试。

前端单元测试的核心流程,就是有“输入”和“输出”,最后“断言”来验证“输出”是否符合预期。Vue.js 3.x组件单元测试分析的四种场景类型,分别有:

  • 渲染测试场景,验证Vue.js组件代码最终DOM是否符合预期;
  • 行为测试场景,验证用户操作Vue.js组件后最终变化是否符合预期;
  • 请求测试场景,用模拟操作方式,验证组件里数据请求逻辑是否符合预期;
  • 兜底测试场景,用传统Vue.js渲染方式,间接验证组件功能是否符合预期。

单元测试覆盖率,就是用覆盖率作为验证单元测试效果的指标。理解了单元测试的作用,对提高开发者的工作效率很有帮助,但也要记得,单元测试不是万能的,存在局限性,你需要根据实际情况做出选择和判断。

思考

这节课都是模板语法写的单元测试,Vue.js 3.x的JSX语法单元测试要怎么写呢?

欢迎你留言参与讨论,如果有疑问也欢迎评论,下一讲见。

完整的代码在这里

精选留言(4)
  • 丫头 👍(1) 💬(1)

    感谢老师帮我扫盲

    2022-12-28

  • ifelse 👍(0) 💬(0)

    学习打卡

    2024-09-11

  • escray 👍(0) 💬(0)

    Error: "coverage.provider: c8" is not supported anymore. Use "coverage.provider: v8" instead 覆盖率的 npm 引擎已经从 c8 升级到 v8 了,是我来的太晚了么 npm i -D @vitest/coverage-c8 会报错 npm i -D @vitest/coverage-v8 就通过了 vitest.config.js 中的配置如下: coverage: { // 覆盖率统计工具 provider: 'v8',

    2024-01-09

  • 高并发 👍(0) 💬(0)

    第三段代码的测试的log文本有点问题, `myMath.add 减法测试成功`,==> `myMath.subtract 减法测试成功`

    2023-08-04