我的 单元测试 入门之旅

一、什么是单元测试?

这次聚焦在单元测试如何验证每个独立功能。要区分单元测试和覆盖率测试的关系,强调单元测试是如何测试每一个步骤是否都正确的手段,覆盖率是衡量指标。

单元测试 就是不让小机器人一次性做完整个蛋糕,而是:

  1. 隔离 :把它关进一个透明的、没有任何其他干扰的迷你厨房(测试环境)。
  2. 专注 :每次只让它严格遵循食谱中的一个步骤(一个单元),并给它准备好的材料(输入)。
  3. 验证 :然后你检查它这一步的产出是否符合你的预期(断言)。

单元测试就是针对程序中最小的可测试单元(通常是函数或模块)进行正确性检验的工作


二、Vue 2 单元测试的运行机制(流程图)

官方 2.6 分支 不用 Jest ,也 不用 Vue Test Utils ;所有单元用例都跑 Karma + Mocha + PhantomJS (或 ChromeHeadless),源码位于 test/unit/

Vue 2 的单元测试核心是 "隔离""模拟" 。我们不启动整个应用,而是单独实例化一个 Vue 组件或调用一个函数,并模拟它所需的一切依赖(如 DOM、外部模块)。

以下是 Vue 2 单元测试的完整流程:

  1. 准备阶段
  • 工具链
    -- Karma:测试运行器
    -- Mocha:测试框架(describe / it
    -- PhantomJS / ChromeHeadless:无头浏览器容器
    -- Sinon-Chai:提供 spy / stub / expect 语法
  • 启动方式
    npm run test:unit → Karma 读取 test/unit/karma.conf.js,把 src/test/unit/specs/ 打包后丢进浏览器跑。
  1. 执行与断言阶段
  • 挂载组件
    直接 new Vue(options).$mount() 到真实 DOM(<div id="app"></div>)。
  • 模拟交互
    手动 dispatchEventwrapper.$el.querySelector('button').click()
  • 断言
    expect(vm.someData).to.equal(...)(Chai BDD 语法)。
  • 典型文件
    test/unit/specs/vdom/create-element.spec.jscomponent.spec.js 等。
  1. 拆解阶段
  • Karma 在每次用例结束后自动刷新 iframe,Mocha 负责清理全局状态,保证用例隔离。

三、简单示例:测试一个计数器组件

假设我们有一个简单的 Vue 2 计数器组件。

源码 (src/components/Counter.vue)

xml 复制代码
<template>
  <div>
    <span class="count">{{ count }}</span>
    <button @click="increment">点我+1</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++;
    }
  }
}
</script>

单元测试 (tests/unit/Counter.spec.js)

我们使用 Jest + Vue Test Utils 来为这个组件编写单元测试。

javascript 复制代码
// 1. 引入必要的依赖
import { shallowMount } from '@vue/test-utils';
import Counter from '@/components/Counter.vue';

// 2. 使用 `describe` 定义一个测试套件,描述你要测试什么
describe('Counter.vue', () => {

  // 3. 使用 `it` 定义一个具体的测试用例
  it('渲染初始计数为 0', () => {
    // Arrange: 挂载组件
    const wrapper = shallowMount(Counter);

    // Assert: 断言组件中的文本包含 0
    // .text() 获取组件文本内容
    // .toContain() 是 Jest 的匹配器,用于判断是否包含
    expect(wrapper.text()).toContain('0');
  });

  it('点击按钮后计数增加 1', async () => {
    // Arrange: 挂载组件
    const wrapper = shallowMount(Counter);

    // Act: 模拟用户点击按钮
    // .find() 查找 button 元素
    // .trigger() 触发一个 'click' 事件
    await wrapper.find('button').trigger('click');

    // Assert: 断言计数现在为 1
    // 注意:这里我们检查的是 data 里的 count,
    // 但更好的做法是检查 DOM 是否更新,因为用户看到的是 DOM
    expect(wrapper.vm.count).toBe(1); // 检查数据
    expect(wrapper.text()).toContain('1'); // 检查 DOM 更新,这样更好!
  });
});

运行测试 (package.json 脚本)

通常在 package.json 中会配置一个脚本:

json 复制代码
{
  "scripts": {
    "test:unit": "jest"
  }
}

然后在终端运行 npm run test:unit。Jest 会找到所有 *.spec.js 文件并执行。

输出结果

如果一切正常,你会看到:

scss 复制代码
PASS  tests/unit/Counter.spec.js
  Counter.vue
    ✓ 渲染初始计数为 0 (5ms)
    ✓ 点击按钮后计数增加 1 (3ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total

如果测试失败(比如你把 increment 方法写错了),Jest 会给出非常详细的错误信息,告诉你预期是什么,实际得到的是什么,帮你快速定位问题。


四、Vue 2 源码自身的单元测试

Vue 2 源码本身的单元测试也是这个原理,只不过测试的不是业务组件,而是框架的核心部分:

  1. 测试响应式系统(Reactivity)

    • 示例:创建一个响应式对象,修改它的属性,然后断言依赖它的"侦听函数"被正确调用了。

    • 代码片段(概念性)

      scss 复制代码
      it('should trigger watcher when dependency changes', () => {
        const obj = reactive({ count: 0 });
        const watcherFn = jest.fn(); // 一个模拟的侦听函数
      
        // 激活依赖收集,让 obj.count 和 watcherFn 建立关联
        watch(watcherFn, () => obj.count);
      
        obj.count++; // Act: 修改依赖
      
        expect(watcherFn).toHaveBeenCalled(); // Assert: 侦听函数被调用
      });
  2. 测试编译器(Compiler)

    • 示例 :给编译器一段模板字符串(如 '<div>{{ msg }}</div>'),断言它编译后生成的渲染函数代码是否正确。
  3. 测试虚拟DOM(Virtual DOM)

    • 示例:断言两个虚拟DOM节点进行 Diff 算法后,产生的补丁(patches)是否正确。

总结:单元测试 vs. 覆盖率测试

特性 单元测试 (Unit Test) 覆盖率测试 (Coverage Test)
目的 验证代码逻辑是否正确 衡量测试用例是否充分
焦点 质量(Quality) 数量(Quantity)
关系 :我们编写测试用例 :由单元测试的执行结果计算得出
比喻 检查食谱每一步的结果对不对 检查食谱有多少步骤被做过了

结论

你为 Vue 2 组件或源码编写的单元测试 ,是产生覆盖率报告前提和原料。你先要有很多好的、覆盖不同场景的单元测试,然后利用覆盖率工具(如 Istanbul)去分析这些测试到底执行了源码的哪些部分,从而发现测试的盲区。

相关推荐
AI大模型-小华6 分钟前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子2 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅2 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni3 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学4 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端
赵庆明老师4 小时前
Vben精讲:21-详解web-antd:tsconfig.json
前端·json·vim
wc885 小时前
微软EDGE浏览器功能学习
前端·学习·edge
Csvn5 小时前
🎯 原生 `<dialog>` 元素:终于可以扔掉一半的自定义弹窗组件了?
前端
Csvn5 小时前
🎨 CSS @layer:用「层叠优先级」终结样式打架的世纪难题
前端