5.4 测试方案代码
下面开始梳理完整的自动化测试方案,整体目录结构如下:
自动化测试方案项目结构
5.4.1 单元测试
(1)对如下方法进行单元测试
1// /src/client/common/js/testUtil.js
2export const sum = (a, b) => {
3 return a + b
4}
复制代码
编写好测试用例
1import { sum } from '../../src/client/common/js/testUtil.js'
2const { expect } = require('chai')
3
4describe('单元测试: sum (a, b)', function () {
5 it('1+1 应该等于 2', function () {
6 expect(sum(1, 1)).to.be.equal(2)
7 })
8})
9// skip可以指定跳过某个分组
10describe.skip('单元测试:金额按千分位逗号分隔的方法 formatMoney (s, type)', function () {...})
复制代码
然后使用 mocha 执行测试用例,输出结果如下
单元测试-公共方法
可以看到两个测试分组有一个测试通过,一个被我们主动跳过。使用mocha执行测试用例时,因为我们指定了测试报告格式--reporter参数为mochawesome,测试报告会被输出为如下的html格式
单元测试报告
为了分析当前测试用例对源代码的覆盖情况,我们使用Istanbul生成测试覆盖率报告
单元测试代码覆盖率报告
代码覆盖率有四个测量维度:
语句覆盖率(statement coverage):是否每个语句都执行了
分支覆盖率(branch coverage):是否每个 if 代码块都执行了
函数覆盖率(function coverage):是否每个函数都调用了
行覆盖率(line coverage):是否每一行都执行了
分别对应上图的 Statements、Branches、Functions、Lines,点击左侧链接可以查看源码测试详情,绿色部分表示已被测试覆盖
代码覆盖详情
关于测试覆盖率,需要强调的是,我们不应该把测试覆盖率的高低作为检验项目质量的标准,只能作为参考。代码覆盖率真正的意义在于帮助开发者找到代码设计的问题,帮助我们发现为什么有的代码没有被测试覆盖到,是代码设计有问题,还是加入了无用代码,它可以指导我们在代码设计中做更好的抽象,写可测试的代码。
(2)React 组件测试
现在有如下的 React 组件
1// /src/client/components/Empty/index.jsx'
2import React, { Component } from 'react'
3import { Icon } from 'antd'
4
5const Empty = (props) => {
6 const placeholder = props.placeholder
7
8 return (
9 <div>
10 <Icon type='meh-o' />
11 <span>{placeholder || '数据为空'}</span>
12 </div>
13 )
14}
15
16module.exports = Empty
17
复制代码
编写测试用例对它进行测试
1import React from 'react'
2import { expect } from 'chai'
3import Enzyme, { mount, render, shallow } from 'enzyme'
4import Adapter from 'enzyme-adapter-react-15.4' // 根据React的版本安装适配器
5import Empty from '../../src/client/components/Empty/index.jsx'
6import { spy } from 'sinon' // 对原有的函数进行封装并进行监听
7
8Enzyme.configure({ adapter: new Adapter() }) // 使用Enzyme 先适配React对应的版本
9
10describe('测试React组件: <Empty />', () => {
11 it('不传入属性时,组件中span的文本为"数据为空"', () => {
12 const wrapper = render(<Empty />)
13 expect(wrapper.find('span').text()).to.equal('数据为空')
14 })
15
16 it('传入属性"我是占位文本"时,组件中span的文本为"我是占位文本"', () => {
17 const wrapper = render(<Empty placeholder='我是占位文本' />)
18 expect(wrapper.find('span').text()).to.equal('我是占位文本')
19 })
20})
复制代码
使用 mocha 执行测试用例会生成如下测试报告,测试通过
React 组件测试报告
测试覆盖率报告如下
React 组件测试覆盖率
!
React 组件代码覆盖详情
5.4.2 接口测试
编写测试用例,使用 supertest 实施接口测试
1const request = require('supertest')
2const { expect } = require('chai')
3const BASE_URL = 'http://127.0.0.1:1990'
4
5describe('接口测试:商户登录测试用例', function () {
6 it('登录接口 /api/user/login', function (done) {
7 request(BASE_URL)
8 .post('/api/user/login')
9 .set('Content-Type', 'application/json') // set header内容
10 .send({ // send body内容
11 user_code: 666666,
12 password: 666666
13 })
14 .expect(200) // 断言希望得到返回http状态码
15 .end(function (err, res) {
16 // console.info(res.body) // 返回结果
17 expect(res.body).to.be.an('object')
18 expect(res.body.data.user_name).to.equal('商户AAAAA')
19 done()
20 })
21 })
22})
复制代码
执行接口测试用例生成如下测试报告
接口测试报告
接口测试报告
5.4.3 e2e测试
编写 e2e 测试用例,使用 selenium-webdriver 驱动浏览器进行功能测试
1const { expect } = require('chai')
2const { Builder, By, Key, until } = require('selenium-webdriver')
3const chromeDriver = require('selenium-webdriver/chrome')
4const assert = require('assert')
5
6describe('e2e测试:商户系统端到端测试用例', () => {
7 let driver
8 before(function () {
9 // 在本区块的所有测试用例之前执行
10 driver = new Builder()
11 .forBrowser('chrome')
12 // 设置无界面测试
13 // .setChromeOptions(new chromeDriver.Options().addArguments(['headless']))
14 .build()
15 })
16
17 describe.skip('登录相关传统用例-跳过', function () {...})
18
19 describe('登录商户系统', function () {
20 this.timeout(50000)
21 it('登录跳转', async () => {
22 await driver.get('http://dev.company.home.ke.com:1990/login') // 打开商户登录页面
23 await driver.findElement(By.xpath('//*[@id="root"]/div/div[2]/div/ul/li[1]/input')).sendKeys(666666) // 输入用户名
24 await driver.findElement(By.xpath('//*[@id="root"]/div/div[2]/div/ul/li[2]/input')).sendKeys(666666) // 输入密码
25 await driver.findElement(By.xpath('//*[@id="root"]/div/div[2]/div/div/button')).click() // 点击登录按钮
26 const currentTitle = await driver.getTitle()
27 await driver.sleep(2000)
28 expect(currentTitle).to.equal('商户管理系统')
29 })
30 })
31
32 after(() => {
33 // 在本区块的所有测试用例之后执行
34 driver.quit()
35 })
36})
复制代码
使用 mocha 执行 e2e 测试用例生成如下测试报告
e2e 测试报告
下图是selenium-webdriver驱动chrome浏览器自动运行,进行功能测试
e2e 测试效果
驱动浏览器执行测试任务
5.4.4 基准测试
假设当前需要测试正则表达式的 test 方法和字符串的 indexOf 方法的性能,我们通常会采用如下方法进行测试:让两个方法分别执行 1000 次,比较哪个耗时长。
1// 判断某个字符串中是否存在特定字符,比较reg.test和str.indexOf性能
2const testPerf = (count) => {
3 var now = new Date() - 1
4 var i = count
5 while (i--) {
6 /o/.test('Hello World!')
7 }
8 console.log(`test方法执行${count}次用时`, new Date() - 1 - now)
9}
10
11const indexOfPerf = (count) => {
12 var now = new Date() - 1
13 var i = count
14 while (i--) {
15 'Hello World!'.indexOf('o') > -1
16 }
17 console.log(`indexOf方法执行${count}次用时`, new Date() - 1 - now)
18}
19
20testPerf(1000)
21indexOfPerf(1000)
复制代码
测试结果如下,因为代码执行较快,两个方法执行 1000 次的时间都为零,无法准确判断代码执行效率
方法性能对比
科学的统计方法是需要多次执行,对大量的执行结果进行采样,我们可以使用工具帮我们完成这件事,如下使用benchmark进行测试``` 1// 判断某个字符串中是否存在特定字符,比较reg.test和str.indexOf性能 2const Benchmark = require('benchmark') 3const suite = new Benchmark.Suite() 4 5// add test 6suite.add('正则表达式test方法', function () { 7 /o/.test('Hello World!') 8}) 9 .add('字符串indexOf方法', function () {10 'Hello World!'.indexOf('o') > -111 })12 // add listeners13 .on('cycle', function (event) {14 console.log(String(event.target))15 })16 .on('complete', function () {17 console.log('Fastest is ' + this.filter('fastest').map('name'))18 })19 // run async20 .run({ 'async': true })```执行测试代码,结果如下,indexOf每秒执行的次数比test每秒执行的次数超出了一个数量级,所以indexOf性能更好
基准测试结果
6 总结
梳理完单元测试、接口测试、功能测试、基准测试的具体实施方案后,结合自动化测试的特点我们可以得出以下结论:
前端要不要进行自动化测试,需要根据具体的项目特点进行判断,对于满足以下条件的代码可以进行自动化测试:
核心功能模块、函数
短期不会发生变化的 UI 组件
提供外部调用的接口
对方法性能进行基准测试
最后,要强调一点,我们的目标是保证代码健壮、可维护,提高开发效率,自动化测试只是一种手段。
作者介绍:
扣丁(企业代号名),目前负责贝壳找房装修平台 B 端业务研发工作。
本文转载自公众号贝壳产品技术(ID:gh_9afeb423f390)。
原文链接:
https://mp.weixin.qq.com/s/pwSzGhRM_25GTjKd2aRt1Q
评论 1 条评论