Vitest
看标题,没错,本次测试用到的是Vitest,为什么选择Vitest而不是Jest,首先,如果看Vitest的文档是可以看到Vitest是兼容Jest的API,另外一个重要的原因就是,Vite和Vitest本来就可以看作一家人,只是分工不同,官方大大是这样说的:Vitest 旨在将自己定位为 Vite 项目的首选测试框架,即使对于不使用 Vite 的项目也是一个可靠的替代方案
为什么要测试
你可能会问,为什么我们要花时间和精力去学习和编写测试呢?尤其是在接触测试的时候,确实会让人感到复杂且难以掌握。毕竟,直接引入组件并手动测试,或者通过控制台打印来调试工具函数,似乎更简单直接。那么,为什么我们还需要专门学习如何编写自动化测试呢?
1. 随着项目规模的增长,手动测试变得不切实际
当你刚开始开发一个简单的组件时,比如 Button.vue
,属性不多,功能也不复杂,手动测试可能确实是一个可行的选择。你可以逐个检查每个属性的行为,确保一切正常。但是,随着项目的不断扩展,组件的属性越来越多,功能也变得更加复杂。此时,手动测试的效率会大大降低,甚至可能出现遗漏的情况。
- 新增属性:每次新增一个属性或功能时,都需要重新手动测试所有相关场景,确保新功能不会影响现有功能。
- 代码更新:当你对代码进行优化、修复 bug 或者重构时,手动测试无法保证所有的旧功能仍然正常工作。你可能会不小心引入新的问题,而这些问题在手动测试中很容易被忽略。
- 团队协作:在一个多人协作的项目中,不同的开发者可能会修改同一个组件。如果没有自动化测试,很难确保每个人的修改都不会破坏其他人的工作。
2. 自动化测试提高了开发效率
自动化测试可以大幅提高开发效率。通过编写测试用例,你可以让计算机自动执行这些测试,而不需要每次都手动点击、输入和检查结果。这不仅节省了时间,还能确保每次代码更改后,所有功能都能正常工作。
- 快速反馈:每次提交代码后,自动化测试可以在几秒钟内告诉你是否有任何问题。这样你可以在早期发现问题,避免后期调试的麻烦。
- 减少回归问题:自动化测试可以帮助你检测到代码更新后是否引入了新的问题,防止回归(即修复了一个问题,却引入了另一个问题)。
- 增强信心:有了可靠的测试覆盖率,开发者可以在修改代码时更加自信,而不必担心会破坏现有的功能。
3. 测试是代码质量的保障
编写测试不仅是为了解决当前的问题,更是为了确保代码的长期可维护性和稳定性。良好的测试覆盖率可以让你的代码更具健壮性,减少潜在的错误和漏洞。
- 文档化行为:测试用例实际上是对组件行为的一种文档化。通过阅读测试代码,其他开发者可以快速理解组件的功能和预期行为。
- 捕获边缘情况:手动测试往往只能覆盖常见的使用场景,而自动化测试可以帮助你捕获一些边缘情况和异常情况,确保组件在各种情况下都能正常工作。
- 持续集成(CI)的支持:自动化测试可以轻松集成到持续集成(CI)管道中,确保每次代码提交后都能自动运行测试,进一步提高代码的质量和可靠性。
4. 测试帮助你更好地设计代码
编写测试的过程不仅仅是验证代码是否正确,它还可以帮助你更好地设计代码。通过编写测试,你可以更清晰地思考组件的接口、行为和边界条件。这种"测试驱动开发"(TDD)的方法可以让你写出更加简洁、易于维护的代码。
- 模块化设计:为了方便测试,你会倾向于将代码分解成更小的、独立的模块。这不仅有助于测试,还能提高代码的复用性和可维护性。
- 清晰的接口:编写测试时,你需要明确组件的输入和输出。这有助于你设计出更加清晰、易用的 API。
- 提前发现设计问题:在编写测试的过程中,你可能会发现一些设计上的问题,比如某些功能难以测试,或者某些逻辑过于复杂。这些问题可以通过调整设计来解决,从而提高代码的整体质量。
测试Button组件
下载Vitest
npm install -D vitest
我们这里使用的是Vue,所以还要下载基于Vue的测试工具,显然Vitest肯定不是只能测试基于Vite构建或使用Vue框架相关的项目
下载vue-test-utils
bash
npm install -D @vue/test-utils
Vitest 配置
安装完 Vitest 后,根文件夹中添加 vitest.config.js
文件中:
perl
/// <reference types="vitest" />
// 上面这行是 TypeScript 的三斜杠指令,用于引用 `vitest` 的类型定义。它确保我们在编写测试时可以使用 Vitest 提供的全局 API(如 `describe`、`test`、`expect` 等),并且获得正确的类型提示。
import { defineConfig } from "vite";
// 从 Vite 中导入 `defineConfig` 函数,用于定义 Vite 的配置对象。Vite 是一个现代的构建工具,支持快速开发和高效的生产构建。
import vue from "@vitejs/plugin-vue";
// 导入 Vite 的 Vue 插件,该插件用于处理 `.vue` 文件,支持单文件组件(SFC)的编译和热更新。
import vueJsx from "@vitejs/plugin-vue-jsx";
// 导入 Vite 的 Vue JSX 插件,该插件允许在 Vue 组件中使用 JSX 语法,适用于需要 JSX 支持的项目。
import VueMacros from "unplugin-vue-macros";
// 导入 `unplugin-vue-macros`,这是一个增强 Vue 开发体验的插件。它提供了多个宏(如 `defineComponent`、`setup` 等),简化了 Vue 组件的编写,并且与其他工具(如 Vite 和 Vue-Test-Utils)兼容。
// Vite 配置对象,用于定义项目的构建和开发环境设置
export default defineConfig({
plugins: [
// 使用 `VueMacros.vite` 来配置 Vite 插件链。`VueMacros` 是一个元插件,它可以自动加载并配置其他插件(如 `vue` 和 `vueJsx`),并且提供额外的功能。
VueMacros.vite({
plugins: {
// 配置 Vue 插件,处理 `.vue` 文件
vue: vue(),
// 配置 Vue JSX 插件,支持 JSX 语法
vueJsx: vueJsx(),
},
}),
],
test: {
globals: true, // 启用全局模式,使得 Vitest 的全局 API(如 `describe`、`test`、`expect` 等)可以直接在测试文件中使用,而不需要显式导入。
environment: "jsdom", // 指定测试环境为 `jsdom`,这是一个虚拟的 DOM 实现,适用于浏览器环境的测试。`jsdom` 可以模拟浏览器的行为,使得我们可以测试与 DOM 相关的功能,而不需要实际打开浏览器。
},
});
开始测试
php
// Button.test.ts
import { describe, test, expect } from "vitest";
import { mount } from "@vue/test-utils";
import Button from "./Button.vue";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import Icon from "../Icon/Icon.vue";
// 使用 vitest 的 describe 函数来定义测试套件,测试 Button.vue 组件
describe("Button.vue", () => {
// 测试基础按钮的功能
test("basic button", () => {
// 使用 mount 函数挂载 Button 组件,并传递 props 和 slots
const wrapper = mount(Button, {
props: {
type: "primary", // 设置按钮类型为 "primary"
},
slots: {
default: "button", // 设置默认插槽内容为 "button"
},
});
// 打印组件的 HTML 结构,用于调试
console.log(wrapper.html());
// 检查按钮是否具有预期的类名 "jd-button--primary"
expect(wrapper.classes()).toContain("jd-button--primary");
// 检查按钮的文本内容是否为 "button"
expect(wrapper.get("button").text()).toBe("button");
// 检查页面中只有一个按钮元素
expect(wrapper.findAll("button").length).toBe(1);
// 触发按钮的点击事件
wrapper.get("button").trigger("click");
// 检查按钮是否触发了 "click" 事件
expect(wrapper.emitted()).toHaveProperty("click");
});
// 测试禁用按钮的功能
test("disabled button", () => {
// 挂载 Button 组件,并设置 disabled 属性为 true
const wrapper = mount(Button, {
props: {
disabled: true,
},
slots: {
default: "button",
},
});
// 检查按钮是否具有 "disabled" 属性
expect(wrapper.get("button").attributes("disabled")).toBeDefined();
// 触发禁用按钮的点击事件
wrapper.get("button").trigger("click");
// 检查禁用按钮是否没有触发 "click" 事件
expect(wrapper.emitted()).not.toHaveProperty("click");
});
// 测试带有图标的按钮
test("icon", () => {
// 挂载 Button 组件,并设置 icon 属性为 "search"
const wrapper = mount(Button, {
props: {
icon: "search",
},
slots: {
default: "button",
},
global: {
stubs: ["FontAwesomeIcon"], // 模拟第三方组件 FontAwesomeIcon
},
});
// 检查按钮内部是否存在 FontAwesomeIcon 组件
const icon = wrapper.findComponent(FontAwesomeIcon);
expect(icon.exists()).toBe(true);
// 检查图标组件的 icon 属性是否为 "search"
expect(icon.props("icon")).toBe("search");
});
// 测试加载中的按钮
test("loading", () => {
// 挂载 Button 组件,并设置 loading 属性为 true
const wrapper = mount(Button, {
props: {
loading: true,
},
slots: {
default: "loading",
},
global: {
stubs: ["Icon"], // 模拟本地的 Icon 组件
},
});
// 打印组件的 HTML 结构,用于调试
console.log(wrapper.html());
// 检查按钮是否具有 "is-loading" 类名
expect(wrapper.get("button").classes()).toContain("is-loading");
// 检查按钮的文本内容是否为 "loading"
expect(wrapper.get("button").text()).toBe("loading");
// 检查按钮内部是否存在 Icon 组件
const icon = wrapper.findComponent(Icon);
expect(icon.exists()).toBe(true);
// 检查图标组件的 icon 属性是否为 "spinner"
expect(icon.props("icon")).toBe("spinner");
// 检查按钮是否具有 "disabled" 属性
expect(wrapper.attributes("disabled")).toBeDefined();
});
});
启动测试,Button就是测试代码所在的文件名 => Button.test.ts
css
npx vitest Button
测试结果如下:
可以看到生成了button节点且测试内容均生效
至于测试中使用到的函数及方法,这里简单介绍下:
-
describe:
- describe 是一个函数,用于对一组相关的测试用例进行分组。它接受一个字符串作为描述(通常描述这个测试组是关于什么功能的),以及一个回调函数,该回调函数包含与该描述相关的 test 函数。
- 使用 describe 可以使测试结构更清晰,特别是当你有大量的测试用例时。
-
test:
- test(有时也被称为 it,取决于测试框架,Vites兼容了Jest,所以写it也可以)是定义单个测试用例的函数。它接受一个字符串作为测试用例的描述,以及一个执行测试逻辑的回调函数。
- 在这个回调函数中,你会使用 expect 函数来断言某些条件是否满足,从而验证代码的正确性。
-
expect:
- expect 是一个函数,用于声明一个"期望"或"断言"。它接受一个值,并允许你链式调用各种"匹配器"(matchers)来检查该值是否满足某些条件。
- 例如,你可以使用 expect(someValue).toBe(anotherValue) 来检查 someValue 是否等于 anotherValue 。
-
mount:
- 函数是 @vue/test-utils 中的一个核心功能,它用于模拟 Vue 组件的挂载过程,并返回一个包装器对象(Wrapper),这个对象提供了一系列的方法和属性,用于访问和操作组件实例及其子组件。
测试的重要性
所以看到这里应该知道测试的重要性了:
- 自动化完成流程,保证代码的运行结果
- 更早发现 Bug
- 重构和升级更加容易和可靠
总结
使用 Vitest 对我们的应用程序进行单元测试是无缝的,与Jest等替代品相比,需要更少的步骤来启动和运行。Vitest 还可以很容易地将现有的测试从 Jest 迁移到Vitest,而不需要进行额外的配置。