11-聊聊前端自动化测试

271 阅读6分钟

以前认为写测试代码的这个时间,还不如直接点点点测试来的快呢,而且那么复杂的逻辑,写测试代码也挺不容易的吧,其实关于复杂以及耗时间这两点,我现在有了些新的认知:

  • 首先,复杂的逻辑写测试代码不容易这点,对于哪些业务、功能需要写测试代码,你要有清晰的认知,知道这点后,那么写代码的时候就要注意了,多个地方用到的尽量提取成公用的代码,逻辑复杂的要尽量往细了拆分,而且通过测试代码也能看出,测试的时候关注的也仅仅是输入输出,中间经历了哪些弯弯折折的,这些都不关心的,那么自己写业务、功能代码的时候就会刻意去注意了,输入输出是什么,如果写的代码中间耦合了很多别的东西,测试代码没办法去写,那你就需要考虑以这种方式写代码是不是不合适,看看有没有更优的写法。
  • 其次,对于写测试代码耗时间,不如点点点测试来的快这个问题,我是这么认为的,或许你们的项目太小了,或许是你的认知水平还没到这一层级,举个例子:有的公司跑测试代码需要连续不停的跑8个小时才能将所有的测试代码跑完,如果换人工去做,那消耗的时间就不可估量了,而且人工去挨个测是非常容易出问题的。对于写测试代码这件事,可能前期会有点麻烦,但是对于以后真是省时省力,造福人类~

测试分类

1. 黑盒测试

常见的开发流程里,都有测试人员,他们不管内部实现机制,只看最外层的输入输出,这种我们称为黑盒测试。比如:你写一个加法的页面,会设计N个用例,测试加法的正确性,这种测试我们称之为E2E测试

2. 白盒测试

还有一种测试叫做白盒测试,我们针对一些内部核心实现逻辑写测试代码,称之为单元测试

3. 集成测试

集成测试就是集合多个测试过的单元一起测试。

编写测试代码的好处

  • 提供描述组件行为的文档
  • 节省手动测试的时间
  • 减少研发新特性时产生的bug
  • 改进设计
  • 促进重构

自动化测试使得大团队中的开发者可以维护复杂的基础代码。让你改代码不再小心翼翼

准备工作

使用工具

在vue中国,推荐使用Mocha+Chai或者Jest

1.要完成测试任务,需要测试框架(跑测试),断言库(编写测试)和编程框架特有的测试套件
2.上面的Mocha是测试框架,Chai是断言库,Jest同时包含两种。
3.vue中组件等测试代码的编写需要vue-test-utils套件支持。

新建vue项目/已存项目中集成

  • 新建vue项目

    • 选择Unit Testing和E2E Testing

    • 单元测试解决方案选择:Jest

    • 端到端测试解决方案选择:Cypress

  • 已存项目中集成

    运行 vue add @vue/unit-jestvue add @vue/e2e-cypress

    注意:安装的时候对node版本是有要求的,需要10.0以上的版本

单元测试

单元测试(unit testing):是对软件中的最小可测试单元进行检查和验证。

编写

安装完@vue/unit-jest后,项目中会有tests/unit目录,在下面创建测试文件,命名规范:必须是*.spec.js的文件名

// test/unit/test.spec.js
function add (num1, num2) {
  return num1 + num2
}

// 测试套件 test suite
describe('test', () => {
// 测试用例 test case
  it('测试add函数', () => {
    // 断言 assert
    expect(add(1, 3)).toBe(3)
    expect(add(1, 3)).toBe(4)
    expect(add(-2, 3)).toBe(1)
  })
})

断言API介绍
1.describe:定义一个测试套件
2.it:定义一个测试用例
3.expect:断言的判断条件
更多断言断言API

执行

npm run test:unit

测试vue组件

vue官方提供了用于单元测试的使用工具库@vue/test-utils

  • 创建组件
// src/components/testUnit/index.vue
<template>
  <div>
    <span>{{ message }}</span>
    <button @click="changeMsg">点击</button> </div>
</template>
<script>
export default {
  data () {
    return {
      message: 'vue-text'
    }
  },
  created () {
    this.message = '更改message'
  },
  methods: {
    changeMsg () {
      this.message = '按钮点击'
    }
  }
}
</script>
  • 测试组件
import testUnit from '@/components/testUnit/'

describe('testUnit.vue', () => {
// 检查组件选项
  it('要求设置created生命周期', () => {
    expect(typeof testUnit.created).toBe('function')
  })
  it('message初始值是vue-test', () => {
    // 检查data函数存在性
    expect(typeof testUnit.data).toBe('function') 
    // 检查data返回的默认值
    const defaultData = testUnit.data()
    expect(defaultData.message).toBe('vue-test')
  })
})

  • 检查mounted之后的预期结果
<template>
  <div>
    <span>{{ message }}</span>
    <button @click="changeMsg">点击</button> </div>
</template>
<script>
export default {
  data () {
    return {
      message: 'vue-test'
    }
  },
  mounted () {
    this.message = '修改后的message'
  },
  methods: {
    changeMsg () {
      this.message = '按钮点击'
    }
  }
}
</script>
import { shallowMount } from '@vue/test-utils'
import testUnit from '@/components/testUnit/'

describe('testUnit.vue', () => {
  // 检查组件选项
  it('要求设置created生命周期', () => {
    expect(typeof testUnit.created).toBe('function')
  })
  it('message初始值是vue-test', () => {
    // 检查data函数存在性
    expect(typeof testUnit.data).toBe('function')
    // 检查data返回的默认值
    const defaultData = testUnit.data()
    expect(defaultData.message).toBe('vue-test')
  })

  it('mount之后data中message数据是修改后的message', () => {
    const wrapper = shallowMount(testUnit)
    // 你可以通过 `wrapper.vm` 访问实际的 Vue 实例
    expect(wrapper.vm.message).toBe('hhhh')
  })
})

  • 点击按钮后测试数据变化
<template>
  <div>
    <span>{{ message }}</span>
    <button @click="changeMsg">点击</button>
  </div>
</template>
<script>
export default {
  data () {
    return {
      message: 'vue-test'
    }
  },
  methods: {
    changeMsg () {
      this.message = '按钮点击'
    }
  }
}
</script>
import { shallowMount } from '@vue/test-utils'
import testUnit from '@/components/testUnit/'

describe('testUnit.vue', () => {
  it('按钮点击后', async () => {
    const wrapper = shallowMount(testUnit)
    // 模拟用户交互
    await wrapper.find('button').trigger('click')
    // 测试数据变化
    expect(wrapper.vm.message).toBe('按钮点击')
    // 测试html渲染结果
    expect(wrapper.find('span').html()).toBe('<span>按钮点击</span>')
    // 等效的方式
    expect(wrapper.find('span').text()).toBe('按钮点击')
  })
})

注意:任何导致操作 DOM 的改变都应该在断言之前 await nextTick 函数。因为 Vue 会对未生效的 DOM 进行批量异步更新,避免因数据反复变化而导致不必要的渲染。

Vue Test Utils

端到端测试

借用浏览器的能力,站在用户测试人员的角度,输入框、点击按钮等,完全模拟用户,这个和具体的框架关系不大,完全模拟浏览器行为。

测试文件

// tests/e2e/spec/test.js
// https://docs.cypress.io/api/introduction/api.html
describe('My First Test', () => {
  it('Visits the app root url', () => {
    cy.visit('/')
    cy.contains('h1', 'Welcome to Your Vue.js App')
  })
})

运行

npm run test:e2e

修改App.vue文件

<template>
  <div id="app">
    <h1>Welcome to Your Vue.js App</h1>
    <router-view />
  </div>
</template>

测试覆盖率

jest自带覆盖率,如果用的mocha,需要使用Istanbul来统计覆盖率。

package.json里修改命令配置:

script:{
    "test": "jest --coverage"
}

package.json里修改jest配置:

"jest": {
    "collectCoverage": true,
    "collectCoverageFrom": ["src/**/*.{js,vue}"],
}

也可以独立配置,在根目录下创建jest.config.js

module.exports = {
    "collectCoverage": true,
    "collectCoverageFrom": ["src/**/*.{js,vue}"]
}

执行:

npm run test

%stmts是语句覆盖率(statement coverage):是不是每个语句都执行了?
%Branch分支覆盖率(branch coverage):是不是每个if代码块都执行了?
%Funcs函数覆盖率(function coverage):是不是每个函数都调用了?
%Lines行覆盖率(line coverage):是不是每一行都执行了?