我的 单元测试 入门之旅

一、什么是单元测试?

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

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

  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)去分析这些测试到底执行了源码的哪些部分,从而发现测试的盲区。

相关推荐
我的xiaodoujiao15 分钟前
从 0 到 1 搭建 Python 语言 Web UI自动化测试学习系列 9--基础知识 5--常用函数 3
前端·python·测试工具·ui
李鸿耀2 小时前
Flex 布局下文字省略不生效?原因其实很简单
前端
皮蛋瘦肉粥_1213 小时前
pink老师html5+css3day06
前端·css3·html5
华仔啊8 小时前
前端必看!12个JS神级简写技巧,代码效率直接飙升80%,告别加班!
前端·javascript
excel8 小时前
dep.ts 逐行解读
前端·javascript·vue.js
爱上妖精的尾巴8 小时前
5-20 WPS JS宏 every与some数组的[与或]迭代(数组的逻辑判断)
开发语言·前端·javascript·wps·js宏·jsa
excel8 小时前
Vue3 响应式核心源码全解析:Dep、Link 与 track/trigger 完整执行机制详解
前端
前端大卫8 小时前
一个关于时区的线上问题
前端·javascript·vue.js
whltaoin8 小时前
中秋赏月互动页面:用前端技术演绎传统节日之美
前端·javascript·html·css3·中秋主题前端
IT派同学9 小时前
TableWiz诞生记:一个被表格合并逼疯的程序员如何自救
前端·vue.js