一、基本概念了解
1、瀑布式开发和敏捷开发的区别
| 区别 | 瀑布式开发 | 敏捷开发 |
|---|---|---|
| 概念 | 严格按照需求分析-设计-编码-测试-维护的步骤顺序进行 | 以用户的需求进化为核心,将一个大的项目拆分成多个相互联系但也可以独立运行的子项目,分别完成,具有更高的灵活性 |
| 优点 | ①阶段清晰,安装顺序依次执行,环环相扣②黑盒模式,各阶段角色只需关注自身的分工 | ①更快交付阶段性成果②开发人员对需求理解更高,适应性更强,变更风险低 |
| 缺点 | ①需求隔离,开发人员对需求的理解程度高低不齐②变更代价大③束缚创造性 | ①由于需求可能会有波动,很难把握整体产品的一致性,资源需要不断地重新分配②很难定义一些标准的流程规范 |
| 适用场景 | 文档驱动,适用于toB的项目,如CRM、OA | 测试驱动,适用于需求不明确、创新性强的项目,或者需要抢占市场的项目 |
2、单元测试和e2e测试的区别:
| 区别 | 单元测试 | e2e测试(端到端测试) |
|---|---|---|
| 含义 | 将代码分解为易于测试的小单元,可以是一个函数、组件、类 | 功能测试,测试整个功能链路能不能走得通 |
| 优点 | ①测试运行的很快②测试精确,定位到具体的函数 | ①可以一次隐式测试很多东西②e2e测试可以确保拥有一个工作系统 |
| 缺点 | ①耗时②尽管单元测试通过,整个应用程序可能还是无法正常工作 | ①运行缓慢②无法查明失败的具体原因③测试很脆弱:如果只是一个组件调整逻辑,就需要重新设计e2e test |
| 关注者 | 开发人员一般更关注单元测试 | 测试人员一般更关注端到端测试 |
3、TDD/BDD/DDD
| 区别 | 测试驱动开发(TDD) | 行为驱动开发(BDD) | 领域驱动开发(DDD) |
|---|---|---|---|
| 概念 | 在开发之前,先编写单元测试用例代码,根据测试代码来明确开发需要实现的业务逻辑代码,注重测试软件。ATDD:验收测试驱动开发,在进行需求分析时就确定需求的验收标准。UTDD:单元测试驱动开发,在写业务代码前先写好功能的测试代码 | 对ATDD的补充,通过编写行为和规范来驱动开发,注重设计软件 | 关注Service层的设计,通过领域设计方法定义领域模型,从而确定业务和应用边界,保证业务模型和代码模型的一致性 |
| 优点 | ①更好地促进代码的设计②更易维护 | ①通过非技术语言扩大了受众,可读性强②聚焦系统行为 | ①系统演进更方便,分为业务复杂型变化和业务数据量变化的演进②更方便测试 |
| 缺点 | ①增加代码量,主要是需要编写一套测试代码②无法保证编写的测试用例一定是用户期望的功能 | 系统改造成DDD复杂,开发熟悉DDD思想困难 |
4、前端测试框架
- jasmine:JavaScript测试框架(BDD:集成测试开发框架)
- MOCHA:本身不带断言库,需要和chai搭配使用
- Jest:目前最流行的前端测试框架,几乎国内大厂都在使用
二、Jest的优点
- 速度快:单独模块测试,比如A和B两个组件,之前测试过了,现在只改动A组件,再次测试,B组件不会再跑一次
- API简单
- 隔离性好:Jest中有很多测试文件可以使用,Jest的执行环境都是隔离的,避免不同的测试文件执行的时候互相影响造成出错
- IDE整合:Jest可以和很多编辑器(VSCode)进行融合,测试变得简单
- 多项目执行:比如写了Node.js的后台项目,用react写了一个前台项目,Jest支持并行运行
- 快速导出覆盖率(测试代码覆盖率):对于一个项目的测试都要出覆盖率的,Jest可以快速给出覆盖率统计结果
三、jest基本使用
1、jest环境搭建
- 创建JestTest文件夹,使用npm init -y初始化
- 安装jest:
npm install jest@24.8.0 -D
2、创建业务逻辑文件和测试文件
- 根目录下创建
utils.js和utils.test.js
utils.js:
const add = (a, b) => a + b;
const reduce = (a, b) => a - b;
const sum = (arr) => arr.reduce((acc, cur) => acc + cur, 0);
module.exports = { add, reduce, sum };
utils.test.js(也可以写成utils.spec.js):
const { add, sum } = require("./utils");
test("数字相加", () => {
expect(add(1, 2)).toBe(3);
});
test("数组求和", () => {
expect(sum([1, 2, 3])).toBe(6);
});
3、执行测试
- package.json中scripts中添加执行命令
"jest": "jest"
- 执行npm run jest
四、Jest配置项
1、概念解释
-
单元测试和集成测试
- 单元测试(unit test):对软件中的最小可测试单元进行检查和验证。前端单元测试就是对一个模块进行测试。
- 集成测试:也叫组装测试或联合测试。在单元测试的基础上,将所有模块按照涉及要求组装称为子系统或系统,进行集成测试
-
代码覆盖率(code coverage):
测试代码占功能性代码和业务代码的比重达到了百分之多少
2、Jest初始化配置
- 执行
npx jest --init,生成jest.config.js
2. 执行
npx jest --coverage,生成coverage文件夹
如果将
coverageDirectory的值改wxm,那么就会生成wxm文件夹
五、Jest中的匹配器
根目录下新建jest.config.js
1、toBe()
test('toBe', () => {
expect('帅逼').toBe('帅逼')
})
test('toBe是严格相等的', () => {
// toBe()匹配器采用的是ES6的Object.is()比较,它基本和===一样
expect(NaN).toBe(NaN) // 通过
expect(+0).toBe(-0) // 不通过
expect(2).toBe('2') // 不通过,和===一样
})
Object.is()比较+0和-0结果为false,比较两个NaN结果为true,这和===是不一样的
2、toEqual()
由于Object.is()比较两个对象时永远不相等,所以比较对象时使用toEqual()
test('toEqual', () => {
expect({ name: 'xx', age: 10 }).toEqual({ name: 'xx', age: 10 }) // 通过
expect({ name: 'xx', age: 10 }).toEqual({ name: 'xx', age: '10' }) // 不通过
})
3、toBeNull()
只匹配null值,不需要传参
test('toBeNull', () => {
expect(null).toBeNull() // 可以使用 xpect(null).toBe(null) 替代
})
4、toBeUndefined()
只匹配undefined值,不需要传参
test('toBeUndefined', () => {
expect(undefined).toBeUndefined() // 可以使用 expect(undefined).toBe(undefined) 替代
})
5、toBeDefined()
test('toBeDefined', () => {
expect(false).toBeDefined() // 只要 expect 方法中有值并且不为 undefined 都可以通过
expect(undefined).toBeDefined() // 不通过
})
6、toBeTruthy()
隐式转为为false的值不会通过,其他值都可以通过
test('toBeTruthy', () => {
const a = 0 // 0 false NaN '' null undefined
expect(a).toBeTruthy()
})
7、toBeFalsy()
隐式转换为false的值才会通过,和toBeTruthy()相反
test('toBeFalsy', () => {
const a = 0 // 0 false NaN '' null undefined
expect(a).toBeFalsy()
})
8、toBeGreaterThan()
test('toBeGreaterThan', () => {
expect(10).toBeGreaterThan(9) // 大于,expect中的参数大于toBeGreaterThan中的参数
})
9、toBeLessThan()
test('toBeLessThan', () => {
expect(10).toBeLessThan(11) // 小于
})
10、toBeGreaterThanOrEqual()
test('toBeGreaterThanOrEqual', () => {
expect(10).toBeGreaterThanOrEqual(10) // 大于等于
})
11、toBeLessThanOrEqual()
test('toBeLessThanOrEqual', () => {
expect(10).toBeLessThanOrEqual(10) // 小于等于
})
12、toBeCloseTo()
test('toBeCloseTo', () => {
expect(0.1 + 0.2).toBeCloseTo(0.3) // 消除浮点精度丢失问题
expect(0.1 + 0.2).toBe(0.3) // 不通过
expect(0.1 + 0.2).toBe(0.30000000000000004) // 通过
})
13、toMatch()
test('toMatch', () => {
expect('大帅比').toMatch('帅比') // 判断字符串是否包含
expect('大帅比').toMatch(/帅比/) // 也可以使用正则
})
14、toContain()
test('toContain', () => {
expect(['大帅比']).toContain('大帅比') // 判断数组中是否包含某项
expect(new Set(['大帅比'])).toContain('大帅比') // 也可以兼容set
})
15、toThrow()
const fn = () => {
throw new Error('fn抛出一个错误')
}
const fn1 = () => {
const a = 0
if (a) throw new Error('fn1抛出一个错误')
}
const fn2 = () => {
throw new Error('fn2抛出一个错误')
}
test('toThrow', () => {
expect(fn).toThrow() // 判断fn中会不会抛出异常,通过
expect(fn1).toThrow() // 不通过
expect(fn2).toThrow('fn3抛出一个错误') // 不通过,fn2中抛出的异常提示文字要包含toThrow中的文字才可以通过
})
16、not
const fn = () => {
throw new Error('fn抛出一个错误')
}
test('not匹配器', () => {
expect(fn).not.toThrow() // 判断fn中会不会抛出异常,如果抛出了异常则不通过,这里是不通过
})
test('not', () => {
expect(100).not.toBe(101) // 通过
})
六、让Jest支持ES6的导入导出
Jest默认支持的是CommonJS规范,也就是Node.js中的语法,只支持module.export = {}/require()这种导入导出,可以使用Babel将代码转换为CommonJS代码
Jest中有一个babel-jest文件,当使用npm run jest时,先去检测开发环境中是否安装了babel,也就是查看有没有babel-core,如果有babel-core就会去查看.babelrc文件,根据配置文件进行转换
操作步骤:
- 安装babel
npm install @babel/core@7.4.5 @babel/preset-env@7.4.5 -D
- 项目根目录创建
.babelrc文件
{ "presets": [["@babel/preset-env", { "targets": { "node": "current" } }]] }
- 重启
可以在脚本中加上这一行,监听xxx.test.js文件,执行npm run jestWatch,有改动时自动执行测试用例
"jestWatch": "jest --watchAll",
- 这样便可以使用ES6的导入了
import { add } from './utils'
七、异步代码的测试方法
安装axios
npm run axios@0.19.0
1、回调函数式
fetchData.js:
import axios from 'axios'
export const getList = async cb => {
const url = 'https://cnodejs.org/api/v1/topics/?page=1&limit=10'
const res = await axios.get(url)
console.log('res>>>>', res.status)
cb(res)
}
fetchData.test.js:
import { getList } from './fetchData'
test('接口测试-回调函数', done => {
getList(res => {
expect(res.status).toBe(200)
done()
})
})
2、直接返回promise
fetchData.js
export const getList = () => {
const url = 'https://cnodejs.org/api/v1/topics/?page=1&limit=10'
return axios.get(url)
}
fetchData.test.js
test('接口测试-返回promise', async () => {
const res = await getList()
expect(res.status).toBe(200)
})
八、Jest中的4个钩子函数
beforeAll(() => {
console.log('所有用例之前执行')
})
beforeEach(() => {
console.log('每个用例之前执行')
})
test('第一个测试', () => {
expect(1).toBe(1)
})
test('第二个测试', () => {
expect('2').toBe('2')
})
afterEach(() => {
console.log('每个用例之后执行')
})
afterAll(() => {
console.log('所有用例之后执行')
})
九、describe()
对测试用例进行分组
describe('分组1', () => {
test('第一个测试', () => {
expect(1).toBe(1)
})
})
describe('分组2', () => {
test('第二个测试', () => {
expect('2').toBe('2')
})
})
十、钩子函数的作用域
- 子级分组继承父级分组的钩子
- 不同分组下的钩子互不干扰
- 分组嵌套就像组件嵌套,先从外到内,再从内到外
1、子级分组继承父级分组的钩子
2、不同分组下的钩子互不干扰
3、分组嵌套就像组件嵌套,先从外到内,再从内到外
十一、only
当测试用例比较多时,只想调试某一个用例,就可以对当前用例加上only修饰,当前文件中其他所有用例就会被skipped掉
describe('一级分组', () => {
test('第一个用例', () => {
expect().toBe(undefined)
})
test.only('第二个用例', () => {
expect().toBe(undefined)
})
test('第三个用例', () => {
expect().toBe(undefined)
})
})