实例入门 Vue.js 单元测试(二)

II. Vue.js 中的单元测试工具

2.1 Jest

不同于"传统的"(其实也没出现几年)的 jasmine / Mocha / Chai 等前端测试框架;Jest的使用更简单(也许就是这个单词的本意“俏皮话、玩笑话”的意思),并且提供了更高的集成度、更丰富的功能。

Jest 是一个由 Facebook 开发的测试运行器,相对其他测试框架,其特点就是就是内置了常用的测试工具,比如自带断言、测试覆盖率工具,实现了开箱即用。

此外, Jest 的测试用例是并行执行的,而且只执行发生改变的文件所对应的测试,提升了测试速度。

配置

Jest 号称自己是一个 “Zero configuration testing platform”,只需在 npm scripts里面配置了test: jest,即可运行npm test,自动识别并测试符合其规则的( Vue.js 项目中一般是 __tests__ 目录下的)用例文件。

实际使用中,适当的在 package.json 的 jest 字段或独立的 jest.config.js 里自定义配置一下,会得到更适合我们的测试场景。

四个基础单词

编写单元测试的语法通常非常简单;对于jest来说,由于其内部使用了 Jasmine 2 来进行测试,故其用例语法与 Jasmine 相同。

实际上,只要先记这住四个单词,就足以应付大多数测试情况了:

  • describe: 定义一个测试套件
  • it:定义一个测试用例
  • expect:断言的判断条件
  • toEqual:断言的比较结果
describe('test ...', function() {
	it('should ...', function() {
		expect(sth).toEqual(sth);
		expect(sth.length).toEqual(1);
		expect(sth > oth).toEqual(true);
	});
});
复制代码

2.2 sinon

sinon

图中这位“我牵着马”的并不是卷帘大将沙悟净...其实图中的故事正是人所皆知的“特洛伊木马”;大概意思就是希腊人围困了特洛伊人十多年,久攻不下,心生一计,把营盘都撤了,只留下一个巨大的木马(里面装着士兵),以及这位被扒光还被打得够呛的人,也就是此处要谈的主角 sinon,由他欺骗特洛伊人 --- 后面的剧情大家就都熟悉了。

所以这个命名的测试工具呢,也正是各种伪装渗透方法的合集,为单元测试提供了独立而丰富的 spy, stub 和 mock 方法,兼容各种测试框架。

虽然 Jest 本身也有一些实现 spy 等的手段,但 sinon 使用起来更加方便。

2.3 Vue Test Utils

Vue Test Utils 是 Vue.js 官方的单元测试实用工具库;该工具库使用起来和用以测试 React 组件的 Enzyme 工具库非常相似

它模拟了一部分类似 jQuery 的 API,非常直观并且易于使用和学习,提供了一些接口和几个方法来减少测试的样板代码,方便判断、操纵和遍历 Vue Component 的输出,并且减少了测试代码和实现代码之间的耦合。

一般使用其 mount()shallowMount() 方法,将目标组件转化为一个 Wrapper 对象,并在测试中调用其各种方法,例如:

import { mount } from '@vue/test-utils'
import Foo from './Foo.vue'

describe('Foo', () => {
  it('renders a div', () => {
    const wrapper = mount(Foo)
    expect(wrapper.contains('div')).toBe(true)
  })
})
复制代码

III. 一个 Vue.js 的单元测试实例

3.1 又一个栗子

import { shallowMount } from "@vue/test-utils";
import Vue from 'vue';
import VueI18n from 'vue-i18n';
import i18nMessage from '@/i18n';
import Comp from "@/components/Device.vue";

const fakeData = { //假数据
  deviceNo: "abcdefg",
  deviceSpace: 45,
  deviceStatus: 2,
  devices: [
    {
      id: "test001",
      location: "12",
      status: 1
    },
    {
      id: "test002",
      location: "58",
      status: 3
    },
    {
      id: "test003",
      location: "199",
      status: 4
    }
  ]
};


Vue.use(VueI18n); //重现必要的依赖
const i18n = new VueI18n({
  locale: 'zh-CN',
  silentTranslationWarn: true,
  missing: (locale, key, vm) => key,
  messages: i18nMessage
});

let wrapper = null;
const makeWrapper = ()=>{
  wrapper = shallowMount( Comp, {
    i18n, //看这里
    propsData: { //还有这里
      unitHeight: 5,
      data: fakeData
    }
  } );
};

afterEach(()=>{ //也很常见的用法
	if (!wrapper) return;
	wrapper = null;
});

describe("test Device.vue", ()=>{

  it("should be a VUE instance", ()=>{
    makeWrapper();
    expect( wrapper.isVueInstance() ).toBeTruthy();
  });

  it("应该有正常的总高度", ()=>{
    makeWrapper();
    expect( wrapper.vm.totalHeight ).toBe( 1230 );
  });

  it("应该渲染正确的设备数量", ()=>{
    makeWrapper();
    expect( wrapper.findAll('.deviceitem').length ).toBe( 3 );
  });

  it("指定的设备应该在正确的位置", ()=>{
    makeWrapper();
    const sty = wrapper.findAll('.deviceitem').at(1).attributes('style');
    expect( sty ).toMatch( /height\:\s*20px/ );
    expect( sty ).toMatch( /bottom\:\s*20px/ );
  });

  it("应该渲染正确的tooltip", ()=>{
    makeWrapper();

	//这里的用法值得注意
    const popper_ref = wrapper.find({ref: 'device_tooltip_test002'});
    expect( popper_ref.exists() ).toBeTruthy();

    const cont = popper_ref.find('.tooltip_cont');
    expect( cont.html() ).toMatch(/所在位置\:\s58/);
  });
  
  it("应该渲染正确的设备分类", ()=>{
    makeWrapper();

    const badge = wrapper.find('.badge');
    expect( badge.exists() ).toBeTruthy();

    expect( badge.findAll('li').length ).toBe(4);
    expect( badge.findAll('li').at(2).text() ).toBe('喷雾设备');
	});
	
  it("当点击了关闭按钮,应该不再显示", (done)=>{ //异步的用例
    makeWrapper();
    wrapper.vm.$nextTick(()=>{ //再看这里
      expect( 
        wrapper.find('.devices_container').exists()
      ).toBeFalsy();
      done();
    });
  });

});
复制代码

这里无需逐条的解释,主要的 API 在 JestVue Test Utils 的文档里都能找到。

其中值得注意的小经验,一是一些异步更新(比如代码中有延时)后正确使用 wrapper.vm.$nextTick;二是对于一些挂载到 document.body 等外部位置的组件元素,要靠 wrapper.find({ref: xxx}) 取得其引用。

3.2 整合到工作流中

写好的单元测试,如果仅仅要靠每次 npm test 手动执行,必然会有日久忘记、逐渐过时,最后甚至无法执行的情况。

有多个时间点可以作为选择,插入自动执行单元测试 -- 例如每次保存文件、每次执行 build 等;此处我们选择了一种很简单的配置办法:

首先在项目中安装 pre-commit 依赖包;然后在 package.json 中配置 npm scripts :

"scripts": {
  ...
  "test": "jest"
},
"pre-commit": [
  "test"
],
复制代码

这样在每次 git commit 之前,项目中存在的单元测试就会自动执行一次,往往就避免了 “改一个 bug,送十个新 bug” 的窘况。

最后

俺叫小枫,一个成天想着一夜暴富的测试员

点赞关注不迷路!!!【三连ღ】,有问题也可私聊哟~(*╹▽╹*)

免费自动化测试资料、面试宝典等,软件测试交流:1140267353 群。这个大家庭等待着你的到来ღ( ´・ᴗ・` )

猜你喜欢

转载自blog.csdn.net/weixin_49346599/article/details/107825845