前端进阶---单元测试入门

什么是单元测试?

  • 对软件中最小可测单元进行测试(如:一个方法function)

引入单元测试的原因?(一般是大的项目时)
- 分模块开发,方便的定位到哪个单元出了问题
- 保证代码质量
- 驱动开发(测试驱动开发)

  • TDD:
    • 测试驱动开发,从需求角度看,即我需要结果是什么,如果不是就是错误的。(需求分析-》编写单元测试-》编写代码使单元测试通过-》重构)
  • BDD:
    • 行为驱动开发,从具体功能角度出发看,即结果应该是什么,如果不是什么就出错。(从业务角度定义目标-》找到实现目标的方法-》编写单元测试-》实现行为-》检查产品)
    • 先写测试用例,再开发满足测试用例

测试的原则

  1. 及时修改和维护
  2. Code review:代码审查
    • 偶尔代码审核
    • 同步
    • 异步(借助工具)
  3. 只测单一的点
  4. 尽量贴近真实
  5. 避免测试中逻辑过于复杂

单元测试详解

单元测试的核心内容

  1. 测试框架
    • 测试运行的基础
    • 框架
      • Jest:基于jasmine,对react友好
      • jasmine:自带assert,mock
      • Mocha:全面适合node和浏览器两个端(适合自定义高的)
      • Qunit出自是Query
  2. 断言库
    • 自己对自己的断言,判断结果
    • 工具:Chai-支持所有风格【常用】,Assert-node环境直接使用,Should,expect-用得少
  3. Mock库
    • 用于屏蔽其他方法的数据影响
    • 工具:sinon
  4. Test runner
    • 跑测试的环境(跑前三个的环境)
    • 工具:karma
  5. 覆盖率工具
    • 提供测试代码的覆盖率工具(单元测试覆盖了多少内容多少行代码)
    • 检测工具: istanbul
  • 示例(测最小单元,对于复杂表单则需要拆分):
/*
* descript:新建一个测试
  it:一个测试用例
  expect: 期待结果
  toequal:等于
*/
// 1. 对于有返回值的方法
descript('this is a test for xxx',()=>{
    it('test function a',()=>{
        expect(a()).toequal(2);
    })
})
// 2. 对于没返回值的方法
descript('this is a test for xxx',()=>{
    b(); // 将div.innerHTML设置为123
    it('test function b',()=>{
        expect(div.innerHTML).toequal('123');
    })
})

Vue-Cli创建的项目:Mocha+Karma进行单元测试

Vue-cli 创建项目,选择mocha+karma进行unit test后创建项目

  • 最终创建的项目,test目录为测试文件目录
|-- test
    |-- karma.conf.js //karma配置文件
    |-- unit
        |-- coverage
        |-- specs
            |-- HelloWorld.spec.js  //一个测试示例
        |-- .eslintrc
        |-- index.js // 引入测试目标文件(spec,coverage)
        |--

karma.conf.js配置文件

  • 一般来说不需要去修改这个配置文件,这里对配置文件中的一些属性名进行说明
module.exports = function karmaConfig (config) {
  config.set({
    // 1. 模拟环境,PhantomJS-无头浏览器,一个JS环境。
    browsers: ['PhantomJS'],
    // 2. 与karma搭配所用到的库, mocha-测试库,sinon-chai-断言和Mock库,phantomjs:无头浏览器用到的模拟环境
    frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'],
    // 3. 用到的文件夹, spec-单元测试,coverage:存覆盖率的
    reporters: ['spec', 'coverage'],
    // 4. 跑单元测试的起点
    files: ['./index.js'],
    // 5. 对代码的预处理(如下:对Index.js先用webpack)
    preprocessors: {
      './index.js': ['webpack', 'sourcemap']
    },
    // 5.1 对webpack的额外配置
    webpack: webpackConfig,
    webpackMiddleware: {
      noInfo: true
    },
    // 测试报告输出的配置
    coverageReporter: {
      dir: './coverage',
      reporters: [
        { type: 'lcov', subdir: '.' },
        { type: 'text-summary' }
      ]
    }
  })
}
  • PhantomJS:是一个基于webkit内核的简易浏览器包

第一个测试用例

  • npm run test:跑测试(初始vue-cli的初始配置)

    • 这里出了个小插曲,首次run test时,报错Cannot load browser "PhantomJS": it is not registered! Perhaps you are missing some plugin?。原因是本机环境没有装过PhantomJS。
    • 解决方案:http://phantomjs.org/download.html这个网址下先去下载好 phantomjs-2.1.1-windows.zip这个包
    • 解压后,把包放到C盘里,C:\Users\Administrator\AppData\Local\Temp\phantomjs,
    • 最后,在项目中重装依赖,再npm run test
  • 第一个测试用例

// HelloWorld.spec.js
import Vue from 'vue'
import HelloWorld from '@/components/HelloWorld'
import axios from 'axios' //测试异步请求时用到
import Promise from 'es6-promise' 
Promise.polyfill();

describe('HelloWorld.vue', () => {
  const Constructor = Vue.extend(HelloWorld)
  const vm = new Constructor().$mount()
  it('should render correct contents', () => {
    expect(vm.$el.querySelector('.hello h1').textContent)
      .to.equal('Welcome to Your Vue.js App')
  })
  // 1. 带返回值的测试用例
  it(`is m1 add two arguments`, () => {
    // m1对应组件helloworld组件中的methods方法m1
    const m1 = vm.m1
    expect(m1(1, 2)).to.equal(3)
  })
  // 2. 一个简单异步方法的测试用例
  it(`async m2 should return 4`, () => {
     // m2对应组件helloworld组件中的methods方法m2
    const m2 = vm.m2
    m2(2, 2, (data) => {
      expect(data).to.equal(4)
    })
  })
  // 3. 一个接口请求的测试用例
  it('接口被请求', () => {
    // 借助工具来屏蔽不用的方法 spy,stub,MOCK 【sinon:一个mock库】
    // spy:可以获取到对应函数的调用 信息,但是不能屏蔽,用于获取参数信息
    // stub :会拦截掉这个方法的作用
    let callback = sinon.stub(axios, 'get')
    let axiosspy = sinon.spy(() => { 
      return 4;
    })
    // 获取HelloWorld组件的异步请求方法 getmes
    const getmes = vm.getmes
    expect(getmes(axiosspy)).to.equal(4);
    callback.restore(); //使用sinon之后都要用resotre进行重置
    console.log(axiosspy.callCount); //用于获取包装的get方法在getmes方法中被调用 了几次
  })
})


  • describe:描述测试
  • it():新建一个测试用例

mocha的钩子函数

  • before: 在执行所有用例之前
  • after: 在每个用例执行之前
  • beforeEach: 在用例执行完之后
  • it.only() : 只执行这一个用例(使用场景:当其他方法测试完了时,只需要测试新出的这个方法时,可以测这一个)

使用Jest库来测试

同样使用vue-cli创建一个初始项目,在选择unit test时,选择jest

  • npm run test直接首次运行时会报错:Option "mapCoverage" has been removed, as it's no longer necessary....
  • 解决办法:修改配置项jest.conf.js
    1. 注释掉mapCoverage: true,
    2. 新增两行verbose: true,testURL:'http://localhost/',
// jest.conf.js
const path = require('path')

module.exports = {
  rootDir: path.resolve(__dirname, '../../'),
  verbose: true,
  testURL:'http://localhost/',
  moduleFileExtensions: [
    'js',
    'json',
    'vue'
  ],
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1'
  },
  transform: {
    '^.+\\.js$': '<rootDir>/node_modules/babel-jest',
    '.*\\.(vue)$': '<rootDir>/node_modules/vue-jest'
  },
  snapshotSerializers: ['<rootDir>/node_modules/jest-serializer-vue'],
  setupFiles: ['<rootDir>/test/unit/setup'],
  //mapCoverage: true,
  coverageDirectory: '<rootDir>/test/unit/coverage',
  collectCoverageFrom: [
    'src/**/*.{js,vue}',
    '!src/main.js',
    '!**/node_modules/**'
  ]
}

jest的一个简单测试用例

  • jest测试用例的写法与mocha类似
  • describe() : 新建一个测试
  • it(): 一个测试用例
  • expect: 断言
  • toEqual(): 理想结果
import Vue from 'vue'
import HelloWorld from '@/components/HelloWorld'

describe('HelloWorld.vue', () => {
  const Constructor = Vue.extend(HelloWorld)
  const vm = new Constructor().$mount()
  it('should render correct contents', () => {
    expect(vm.$el.querySelector('.hello h1').textContent)
      .toEqual('Welcome to Your Vue.js App')
  })
  it('just a test', () => {
    const m1 = vm.m1;
    expect(m1(1,2)).toEqual(3)
  })
})
发布了114 篇原创文章 · 获赞 146 · 访问量 25万+

猜你喜欢

转载自blog.csdn.net/Sophie_U/article/details/103496247