我的 单元测试 入门之旅

一、什么是单元测试?

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

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

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

相关推荐
葡萄城技术团队12 分钟前
【SpreadJS V18.2 新版本】设计器新特性:四大主题方案,助力 UI 个性化与品牌适配
前端
lumi.21 分钟前
Swiper属性全解析:快速掌握滑块视图核心配置!(2.3补充细节,详细文档在uniapp官网)
前端·javascript·css·小程序·uni-app
调皮LE23 分钟前
可放大缩小弹窗组件,基于element-ui的vue2版本
前端
陈随易26 分钟前
10年老前端,分享20+严选技术栈
前端·后端·程序员
我的小月月32 分钟前
基于Canvas实现的网页取色器功能解析
前端
芝士加1 小时前
还在用html2canvas?介绍一个比它快100倍的截图神器!
前端·javascript·开源
阿虎儿1 小时前
React 引用(Ref)完全指南
前端·javascript·react.js
Ratten1 小时前
解决 error when starting dev server TypeError crypto$2.getRandomValues
前端
coding随想1 小时前
深入浅出DOM3合成事件(Composition Events):如何处理输入法编辑器(IME)的复杂输入流程
前端
六月的雨在掘金1 小时前
狼人杀法官版,EdgeOne 带你轻松上手狼人杀
前端·后端