tdd/bdd

测试策略

测试并不能直接减少bug,但可以间接减少代码反复修改出现的bug(每次修改极可能破坏原有系统功能)

测试有:

  • 单元测试
  • 组件测试(含快照测试Snapshot)
  • 契约/接口测试(从api级别对代码进行测试)
  • E2E测试。端对端测试,集成测试(从用户的级别进行测试)

单元测试:测试一个模块,对一个单元进行测试
集成测试:多个模块测试,同时对单元内的其他文件、依赖做统一做了测试

单元测试

步骤:
1)选单元测试框架
2)创建测试规则
3)制定测试覆盖率的最小值

一般都采用三段式风格:given-when-then

  • 假设:一个上下文,指定完成测试所需要的条件
  • 当:进行一系列操作,即执行操作,如单机某个按钮
  • 那么:得到可观察的后果,即检测断言,如判单按钮是否被隐藏

正常情况下,应该测试大部分函数,但没有足够的时间,那就可以遵循测试的规则:

  • 必须进行测试的式通用、公用的utils函数
  • 复杂交互操作需要进行一定的测试
  • 网络请求可以交给契约测试,或者不进行测试

单元测试通常只用于测试代码中的逻辑,而对于隐藏在模板中的逻辑来说,单元测试往往难以进行测试。因此,在编写业务逻辑的时候,尽可能把代码写在 JavaScript 或 TypeScript 代码中。可以尝试在项目中采用驱动测试开发,以测试来驱动业务逻辑。采用这种方式来开发,可以保证功能都得到测试

组件测试

组件测试时指对项目中编写的组件进行测试。对组件测试的两部分

  • 组件功能测试,即单元测试
  • 组件行为的测试,当点击组件的按钮,除了对应的模型、数据发送变化。对应的dom也发生改变,对dom的行为测试–快照测试。

快照常用于测试组件的配置文件,比如生成一个快照,你再增加新参数时,会提示你是否通过这个参数,如果通过就按w进入模式,按u更新成新的快照

就是运行测试的时候,把结果存一份,之后可以用来对比,对比不上就测试不通过

契约/接口测试

契约测试Contract Test,即对前后端约定的api进行测试
测试对象不同

E2E测试

其表现行为类似真实的用户行为:启动浏览器,模拟用户行为进行操作。
测试要花较长的时间编写,由于需要真实运行,运行时间也较长
测试场景:经常出错的、核心的功能

angular有自己的E2E测试框架Protractor

在选定E2E测试的时候,可以考虑是否采用BDD的方式进行:
BDD,英文全称 Behavior Driven Development,中文含义为行为驱动开发,它是一种敏捷软件开发的技术,它鼓励软件项目中的开发者、QA 和非技术人员或商业参与者之间的协作。

与一般的自动化测试(如单元测试、服务测试、UI 测试)不一样,BDD 是多方参与的测试开发方式。
如在使用 Protractor 写 Angular 的 E2E 测试的时候,所有的测试都是前端测试人员编写的。
BDD 最重要的一个特性是:由非开发人员编写测试用例,而这些测试用例是使用自然语言编写的 DSL(领域特定语言)

// BDD测试在代码底层的实现
/*	当我在网站的首页,输入用户名demo、密码123
	结果:提交登录信息,用户跳转到欢迎页面
*/
defineSupportCode(function(Given, When, Then){
	Given('当我在网站的首页', function(){
		return this.driver.get('http://0.0.0.0:7272/')
	})
	When('输入用户名{string}', function(text){
		return this.driver.findElement(By.id('username_field')).sendKeys(text)
	})
	Then('输入密码{string}', function(text){
		return this.driver.findElement(By.id('password_field')).sendKeys(text)
	})
})

expect 断言
vscode 工具jest

jest基础api -> 异步测试 -> mock技巧
快照 -> timer测试 -> Dom测试

vue:Vue-test-utils
react:Enzyme

tdd 单元测试 -> bdd集成测试

测试底层原理

底层原理:一段测试代码,通过这段代码去运行其他代码,预期他的结果,然后判断输出结果是否与预期相等

function expect(result){  // 实际结果
    return {
        toBe: function(actual){  // 预期   
            if(result !== actual){
                throw new Error(`预期值和实际值不相等,预期为${actual},实际结果为${result}`)
            }else{
                console.log('通过测试')
            }
        }
    }
}
function test(desc, fn){
    try{
        fn()
    }catch(e){
        console.log(`${desc} 没有通过测试`)
    }
}
function add(a, b){
    return a+b
}
test('测试加法 3+7', ()=>{
    expect(add(3, 7)).toBe(8)
})

jest基础

npm i jest@24.8.0 -D   // 当安装了jest其实就顺带安装了babel-jest

"test":"jest" // 运行时会去找xxx.test.js的后缀进行测试
"test":"jest --watchAll" // 监听所有文件,自动跑测试用例,不用每次修改去重新启动

// 初始化配置
npx jest --init

npx jest --coverage  // 生成代码覆盖率报告

// 修改jest.config.js
coverageDirectory:"dellee"  // 会把报告生成到dellee文件夹,意思就是把覆盖率报告生成到哪个文件夹下
npm @babel/core@7.4.5  @babel/preset-env@7.4.5  -D

// .babelrc
{
	"presets":[  
		["@babel/preset-env"{ //第二个参数: 是@babel/preset-env的配置项
			"targets":{
				"node":"current" // 根据当前node环境下去做代码进行转换
			}
		}] 
	]
}

在这里插入图片描述

在这里插入图片描述

匹配器 matchers

  • tobe 就是匹配器,类似Object.is
  • toEqual 只会匹配内容,不会匹配引用
  • toBeNull 判断是不是空内容 null(不能用来测试undefined)
  • toBeUndefined 判断是不是undefined (空字符串和null都不通过)
  • toBeDefined判断是否被定义过的(undefined|)
  • toBeTruthy 是否为真(0,‘’ 等都不通过)
  • toBeFalsy 是否为假
  • not 取反的匹配器
  • 数字
    • toBeGreaterThan(a) 比某个数a大
    • toBeLessThan(a) 比某个数a小
    • toBeGreaterThanOrEqual(a) 大于等于a
    • toBeCloseTo 用于浮点数 expect(0.1+0.2).toBeCloseTo(0.3)为true
  • 字符串
    • toMatch(a)包含某个字符串a
  • Array、Set
    • toContain(a)数组里是否包含某个对象a expect(arr).toBeCloseTo(‘a’)
  • 异常
    • toThrow 能否抛出异常
// 测试对象内容
test('测试10与10相匹配', ()=>{
	const a={one:1}
	expcet(a).toBe({one:1}) // 因为引用地址不同,所以为false
	expcet(a).toEqual({one:1}) // true
})

test('测试对象内容相等', ()=>{
	const a=nul
	expcet(a).toBeNull()
	const b=1
	expect(b).not.toBeFalsy()  // 判断是否不等于false,所以为true
})

// 能否抛出异常
const throwNerrorFunc=()=>{
	throw new Error('this is a new error')
}
test('toThrow',()=>{
	expect(throwNerrorFunc).toThrow() // true
	expect(throwNerrorFunc).toThrow('this is a old error')  // false
	expect(throwNerrorFunc).not.toThrow()  // false
})

官网文档

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

模式

按w进入模式切换:

  • f:不想把通过的测试用例再跑一遍
  • o:只会测试当前改变的测试用例(需要使用git来管理代码,会记录哪个文件改变了)
  • a:任何一个文件发生改变,都会把所有测试用例跑一遍
  • t:根据表达式来过滤哪些测试用例需要执行
  • q:退出对代码的监控
  • enter:按回车重新运行测试
  • p:会去找包含你输入名字的测试用例
// 初始git仓库
git init
// 存到仓库
git add .
// 提交
git commit -m 'version1'
npm run test >  点w进入o模式


"test":"jest --watchAll"  // 测试所有用例
"test":"jest --watch"  // 直接进入o模式,只会测试有变更的文件

git checkout master.test.js

异步代码测试

  • 回调异步类型的用done参数
    在参数里面写done,并调用,表示调用了done函数才执行完成
import axios from 'axios'
export const fetchData=(fn)=>{
	axios.get('http://xxx').then((response)=>{
		fn(response.data)
	})
}
import {fetchData} from './fetchData'
test('fecthData 返回结果为 {success:true}',(done)=>{
	// fetchData()能够正常执行,这个测试用例就结束了,不会等到回调函数执行完才结束
	fecthData((data)=>{ 
		expect(data).toEqual({
			success: true
		})
		// 只有回调执行完毕了才结束
		done()
	})
})
  • 必须走一次expect语法 expect.assertions(1)
import axios from 'axios'
export const fetchData=(fn)=>{
	return axios.get('http://xxx')
}
// 测试正确
test('fecthData 返回结果为 {success:true}',(done)=>{
	// fetchData()能够正常执行,这个测试用例就结束了,不会等到回调函数执行完才结束
	return fecthData().then((response)=>{
		expect(resopnse.data).toEqual({
			success: true
		})
	})
})

// 测试错误
test('fecthData 返回结果为 404',()=>{
	// 必须走一次expect语法
	expect.assertions(1)
	// 如果正确就不走catch
	return fecthData().catch((e)=>{
		expect(e.toString().indexOf('404')-1).toBe(true)
	})
})
  • 使用不同的匹配器去测试用例
// 测试成功情况
// toMatchObject返回的数据,只要包含这块内容就可以
test('fectchData 返回结果为{success: true}',()=>{
	return expect(fetchData()).resolves.toMatchObject({
		data:{
			success: true
		}
	})
})
// 可以改成异步函数
test('fectchData 返回结果为{success: true}', async ()=>{
	await expect(fetchData()).resolves.toMatchObject(...)
})
// 或者改成这种
test('fectchData 返回结果为{success: true}', async ()=>{
	const response = fetchData()
	expect(response.data).toEqual({ success: true })
})

// 测试失败情况
test('fectchData 返回结果为404',()=>{
	// 取数据时,希望请求是失败的,失败就会抛出异常
	return expect(fetchData()).rejects.toThrow() 
})

// 捕获失败
test('fectchData 返回结果为404',async()=>{
	expect.assertions(1) // 即使是正确的,也必须往catch走
	try{
		await fetchData()
	}catch(e){
		expect(e.toString()).toEqual('Error: Request failed...')
	}
})

钩子函数

jest执行过程中的某些具体时刻,会被jest执行调用的函数
执行顺序:beforeAll > beforeEach > afterEach > afterAll

// counter.test.js
import Counter from './counter'
// const counter= new Counter 测试用例add的结果会影响到minus的结果,不推荐这种写法
let counter= new Counter
// 所有测试用例执行前调用beforeAll(),让他帮你去做准备,但是测试用例公用同一个counter,也会有影响
beforeAll(()=>{
	// counter= new Counter()
})
// 每个测试用例执行之前都会调用
beforeEach(()=>{
	counter= new Counter()
})
// 每个测试用例执行之后会被调用
afterEach(()=>{console.log('afterEach')})
// 等待所有的测试用例都结束后执行
afterAll(()=>{console.log('afterAll')}) 

test('测试add方法',()=>{
	counter.add()
	expect(counter.number).toBe(1)
})

test('测试minus方法',()=>{
	counter.minus()
	expect(counter.number).toBe(1)
})
  • describe分组语法
describe('测试counetr代码', ()=>{
	describe('测试增加代码', ()=>{
		// ...
	})
	describe('测试减少代码', ()=>{
		// ...
	})
})
  • 钩子函数作用域
  • 一个describe下的钩子函数只对他自己有效
describe('测试counetr代码', ()=>{
	beforeAll(()=>{console.log('beforeAll')})
	beforeEach(()=>{console.log('beforeEach')})
	afterEach(()=>{console.log('afterEach')})
	afterAll(()=>{console.log('afterAll')}) 
	describe('测试增加代码', ()=>{
		// beforeEach只会对里面的test生效
		beforeEach(()=>{console.log('beforeEach test add')})
		beforeAll(()=>{console.log('beforeAll test add')})
	})
	describe('测试减少代码', ()=>{
		// ...
	})
})
// 执行顺序:每个钩子先执行外部的再执行内部,由外到内
beforeAll > beforeAll test add > beforeEach > beforeEach test add > 执行测试用例 > afterEach > afterAll
  • test.only 只执行当前的,其他不会被执行
test.only ('测试add方法',()=>{...})
  • 如果逻辑不写在钩子函数里或者test里,会被最先执行
    所以要把准备的代码放到钩子函数里,避免遇到错误,定位困难
describe('测试counetr代码', ()=>{
	console.log('describe 1')
	beforeAll(()=>{console.log('beforeAll')})
	describe('测试增加代码', ()=>{
		console.log('describe 2')
	})
	describe('测试减少代码', ()=>{
		console.log('describe 3')
	})
})
// 执行顺序:describe 1 > describe 2 > describe 3 >  beforeAll 

mock

  • mock函数const func = jest.fn(),捕获函数的调用和返回结果,以及this指向和调用顺序
  • 设置返回结果
    • 模拟每次返回的结果func.mockReturnValue('abc')
    • 模拟一次返回的结果func.mockReturnValueOnce('abc'),可链式调用
  • 改变函数的内部实现
    • 用jest模拟axios,这样就不会真正请求数据jest.mock('axios')
    • 模拟每次返回的结果axios.get.mockResolvedValue({data: 'hello'})
    • 模拟一次返回的结果axios.get.mockResolvedValueOnce({data: 'hello'}),可链式调用
// demo.js
// 接收callback然后去执行
export const runCallback = (callback) => {
	callback()   // 注意没有return返回
}

// demo.test.js
import {runCallback } from './demmo'
// 使用jest生成一个函数
test('测试 runCallback ',()=>{
	/* const func = jest.fn()
		runCallback(func) // func被调用了,就说明函数执行了
		runCallback(func)  
		// expect(func).toBeCalled()
		// 判断是否被调用两次
		expect(func.mock.calls.length).toBe(2)
	*/
	const func = jest.fn()
 	/* const func = jest.fn(()=> '456')
 		底层写法,跟上面的写法一致
		func.mockImplementation(()=>{
			console.log('aaa')
			return '456'
		})
		func.mockImplementationOnce(()=>{
			// 跟mockReturnValueOnce的区别是,可以在里面写额外的逻辑
			console.log('hhhh')
			return '456'
		})

		func.mockImplementation(()=>this)  等价于 func.mockReturnThis
		expect(func.mock.results[0].value).toBeUndefined() 
	*/
 	// func.mockReturnValueOnce('abc') // 第一次被执行时返回'abc',其他都是undefined
 	// func.mockReturnValueOnce('abc1').func.mockReturnValueOnce('abc2') // 可以链式调用
 	func.mockReturnValue('abc') // 每次都返回'abc'
 	runCallback(func)
 	runCallback(func)
 	runCallback(func)
 	expect(func.mock.results[0].value).toBe('abc')
 	// expect(func.mock.results[1].value).toBe('abc') // 第二次调用
	console.log(func.mock) // func的this指向undfined
})

func.mock的返回结果instances

// demo.js
// 函数接收一个类,创建实例
export const createObject = classItem => new classItem()

// demo.test.js
test('测试 createObject ', ()=>{
	const func = jest.fn()
	createObject(func) 
	console.log(func.mock)  // instances: [mockConstructor{}],每次func运行时的this指向,指向new 构造出的对象mockConstructor
})

改变函数内部的实现

// demo.js
import axios from 'axios'
export const getDate = () => axios.get('/api').then(res=>res.data)

// demo.test.js 
// 不会真正发生ajax请求接口,只要确任这个异步请求发送了,后端返回什么内容不会测试(如果有1w个测试用例就调用1w次,不靠谱,所以不会真正请求)
import axios from 'axios'
jest.mock('axios') // 用jest模拟axios,这样就不会真正请求数据
test('测试 createObject ', ()=>{
	// axios.get.mockResolvedValueOnce({data: 'hello'}) // 模拟一次的返回结果
	axios.get.mockResolvedValue({data: 'hello'}) // 模拟成功请求的结果,把异步获取内容变成同步获取
	await getData().then((data)=>{ // 这里的data就是hello
		expect(data).toBe('hello')
	})
	await getData().then((data)=>{ 
		expect(data).toBe('hello')
	})
})

snapshot快照测试

  • toMatchSnapshot() 快照匹配,会自动生成快照
  • toMatchInlineSnapshot()行内快照,测试后,快照会被放到测试用例里
// demo.js
export const generateConfig = () =>{
	return {
		server: 'http://locahost',
		port:8080,
		domain:'locahost',
		time:new Date() 
	}
}

// demo.test.js
import {generateConfig} from './demo'
test('测试 generateConfig 函数', ()=>{
	/* expect(generateConfig()).toEqual({
		server: 'http://locahost',
		port:8080
	})*/
	// 会把generateConfig函数生成得结果存到快照里面,生成一个快照文件,只要demo.js被修改了,快照就会告诉你出错,可以按u键更新快照
	// expect(generateConfig()).toMatchSnapshot()
	expect(generateConfig()).toMatchSnapshot({
		time: expect.any(Date)
	})
})
// 要先安装prettier,不然没法使用
// npm i prettier -D
test('测试 generateConfig 函数', ()=>{
	expect(generateConfig()).toMatchInlineSnapshot({ // 会把快照生成到test测试用例里面
		time: expect.any(Date)
	},
	{
		"domain": "localhost"
		...
	}
	)
})
/* 匹配不上,
按u可更新快照,
按i模式一个个去确任,
按s可跳过
*/

mock深入

对异步请求进行mock

  • 测试请求方法
// demo.js
import axios from 'axios'
export const fetchData=()=>{
	return axios.get('/').then(res=>res.data)
}
// {data:"(function(){return '123'})()"}


// demo.test.js
import {fetchData} from './demo'
import axios from 'axios'
// 用jest模拟axios
jest.mock('axios') 
test('fetchData 测试', ()=>{
	Axios.get.mockResolvedValue({
		data:"(function(){return '123'})()"
	})
	return fetchData().then(data=>{
		expect(eval(data).toEqual('123'))
	})
})
  • 模拟fetchData方法
    • 编写一个文件,用文件内容去替换掉真实的请求方法jest.mock('./demo')
    • 不从模拟的拿数据jest.requireActual('./demo')
// demo.js
import axios from 'axios'
export const fetchData=() => axios.get('/').then(res=>res.data)
export const getNumber = () => 123

// __mocks__/demo.js  用__mocks__文件夹里面得demo.js去替换外面的demo.js
export const fetchData=()=>{
	return new Promise((resolved, reject)=>{
		resolved("(function(){return '123'})()")
	})
}

// demo.test.js
// 模拟demo文件的方法
jest.mock('./demo') 
// 取消对demo文件的模拟,模拟不了就会用真正根目录下的demo文件
// jest.unmock('./demo')

import { fetchData } from './demo'
const {getNumber} = jest.requireActual('./demo') // 不是模拟的,拿真正的内容

test('fetchData 测试', ()=>{
	return fetchData().then(data=>{
		expect(eval(data)).toEqual('123')
	})
})

test('getNumber 测试', ()=>{
	expect(getNumber()).toEqual(123)
})

// 如果不用这些,可以开启jest.config.js里面的automock: true

对定时器进行mock

定时器是异步,可以用done,测试中定时器太长,不可能等很久,所以用mock模拟定时器,让它马上测试,避免等待时间

  • 模拟定时器jest.useFakeTimers()
  • 立即执行jest.runAllTimers(),把所有代码都运行了
  • jest.runOnlyPendingTimers()只想外部定时器执行
// timer.js
export default (callback) => {
	setTimeout(()=>{
		callback()
		setTimeout(()=>{
			callback()
		}, 3000)
	}, 3000)
}

// demo.test.js
import timer from './timer'

/* done的用法
test('timer 测试', (done)=>{
	//timer是异步函数,不会等你里面的东西执行完再去测试成功或失败
	timer(()=>{ 
		expect(1).toBe(1) // 如果3秒后被执行为true,说明是ok的
		done()
	})
})
*/

// 模拟定时器
jest.useFakeTimers()
test('timer 测试', ()=>{
	// 判断fn是否被执行
	const fn = jest.fn()
	timer(fn) // 还要等3秒
	// jest.runAllTimers() // 马上执行,避免等待时间
	// jest.runOnlyPendingTimers() // 只想外部定时器执行
	jest.advanceTimersByTimer(3000) // 快进3秒,比如2秒是不会执行函数的
	expect(fn).toHaveBeenCalledTimes(1) // 如果是嵌套定时器,次数要改为2
	jest.advanceTimersByTimer(3000) 
	expect(fn).toHaveBeenCalledTimes(2) 
})

对类的mock

创建实例对象,调用方法执行,判断结果,就可以对类进行测试

// util.js
class Util{
	init(){ // 非常复杂 }
	a(){ // 非常复杂 }
	b(){ // 非常复杂 }
}
export default Util

// util.test.js
import Util from './util'
let util = null
beforeAll(()=>{
	util = new Util()
})
test('测试a方法', ()=>{
	expect(util.a(1, 2)).toBe('12')
})

不需要获取最终结果,可以不用让它真实执行,只想知道a和b的方法有没有执行过。可以对类进行模拟,创建简单的类,让简单的util类去执行,不是真实执行a和b的方法,就可以节约性能
jest.mock(类)就是把类变成jest.fn,就可以测试mock实例上的方法是否被调用过

// demo.js 调用util的方法
import Util from './util'
const demoFunction = (a, b) =>{
	const util = new Util()
	util.a(a)
	util.b(b)
}
export default demoFunction

// demo.test.js 重点测试 a和b是否被执行过,而不是返回结果
// mock模拟util,就是把Util、Util.a、Util.b都变成jest.fn,这样就可以测试util是否被调用了
jest.mock('./util') 
/* 原理:jest.mock模拟类,会自动把类的构造函数和方法变成jest.fn
	const Util = jest.fn() // 这就是虚构的Util
	Util.a = jest.fn()
	Util.b = jest.fn()
*/
// 引入之后,执行demoFunction函数,就会去执行mock的util
import Util from './util' 
import demoFunction  from './demo'

test('测试 demoFunction', ()=>{
	// 只想知道a和b是否被执行过
	demoFunction()   
	expect(Util).toHaveBeenCalled()
	expect(Util.mock.instances[0].a).toHaveBeenCalled()
	expect(Util.mock.instances[0].b).toHaveBeenCalled() // mock实例的方法是否被调用
	console.log(Util.mock.instances[0])
})

jest.mock()的模拟是自动化模拟,可以把Util、Util.a、Util.b自动变成jest.fn,如果对它的模拟不满意,可以自己进行更深入模拟

// __mocks__/util.js
const Util = jest.fn(()=>{ console.log('constructor') })
Util.prototype.a = jest.fn(()=>{ console.log('a') })
Util.prototype.b = jest.fn(()=>{ console.log('b') })
export default Util

// 也可以直接在文件中写入
jest.mock('./util', ()=>{
	const Util = jest.fn(()=>{ console.log('constructor') })
	Util.prototype.a = jest.fn(()=>{ console.log('a') })
	Util.prototype.b = jest.fn(()=>{ console.log('b') })
	return Util
}) 

对DOM节点操作进行测试

node环境不具备dom,而jest在node环境下模拟了一套dom的api,jsDom

// demo.js
import $ from 'jquery'
const addDivToBody = () => {
	$('body').append('<div/>')
}
export default addDivToBody 

// demo.test.js
import addDivToBody from './demo'
import $ from 'jquery'
test('test addDivToBody ',()=>{
	// 执行一次函数,增加一个div
	addDivToBody() 
	expect($('body').find('div').length.toBe(2))
})

vue的集成

Test Driven Development(TDD)测试驱动的开发

tdd开发流程:还没开发就先写测试用例

  1. 编写测试用例
  2. 运行测试,测试用例无法通过测试
  3. 编写代码,使用测试用例通过测试
  4. 优化代码,完成开发
  5. 重复上述步骤

tdd优势:即使解决,减少回归bug;会使代码结构组织合理、代码质量更好;测试覆盖率高;不容易出现错误代码

// 使用脚手架创建项目
npm @vue/cli -g
vue create jest-vue

先babel转换,再jest测试

// jest.config.js
{
"moduleFileExtensions": ["vue", "js", "json", "jsx", "ts", "tsx", "node"], // 后缀
"transform": { // 做模块转换
    "^.+\\.vue$": "vue-jest", // 识别vue语法
    ".+\\.(css|style|less|png|svg|jpg|woff)":"jest-transform-stub" // 把静态资源转换为字符串,只对逻辑做测试,不对样式做测试,遇到静态资源没必要解析它,所以转换为字符串就可以
    "^.+\\.jxs$": "babel-jest" // 把es6语法转成es5语法
  }
transformIgnorePatterns: ['/node_modules/'] // 不需要被转换
testPathIgnorePatterns: ['\.eslintrc\.js'] // 不会对这些文件进行测试
moduleNameMapper: {'^@/(.*)$': '<rootDir>/src/$1'} // 做模块映射,以@开头的都到根目录src下找,把别名映射到真正模块上
snapshotSerializers:['jest-serializer-vue'] // 用第三方模块对快照进行格式化
testMatch: ["<rootDir>/tests/unit/*.(js|jsx|ts|tsx)"], // jest 的时候到哪里找测试文件
/*testMatch: [
    "**/test/**/*.js",
    "**/__tests__/**/*.spec.js",
    "**/__tests__/**/*.spec.ts"
]*/
testURL:'http://localhost/', // 模拟浏览器当前对应地址多少
watchPlugins: [ // 协助jest交互的插件
	'jest-watch-typeahead/filename',
	'jest-watch-typeahead/testname'
]
}

在这里插入图片描述
在这里插入图片描述

@vue/test-utils

import {shallowMount} from '@vue/test-utils'
import HelloWorld from '@/component/HelloWorld '

describe('HelloWorld.vue', ()=>{
	it('renders props.msg when passed', ()=>{
		const msg = 'new message'
		/* 浅渲染shallowMount,只渲染自己,不会渲染子组件
			mount会把子组件也渲染
		*/
		const wrapper = shallowMount(HelloWorld, { 
			propsData: {msg}
		})
		// expect(wrapper.text()).toMatch(msg)
		expect(wrapper.findAll('.blue').length).toBe(1)
		//console.log(wrapper.props('msg'))
		expect(wrapper.props('msg')).toEqual(msg)
		wrapper.setProps({msg: 'hello'})
		expect(wrapper.props('msg')).toEqual('hello')
	})
})

快照测试:

it('组件渲染正常', ()=>{
	const wrapper = shallowMount(HelloWorld, { 
		propsData: {msg:'abc'}
	})
	// expect(wrapper).toEqual('hello')
	/* 只要组件能够渲染也不出错的话,就会生成快照
		错误时,如果确认组件的是正确无误的,按u更新快照
		所以快照测试可以及时发现dom组件中发生的变化
	*/
	expect(wrapper).toMatchSnapshot()
})

react的集成

// 使用react脚手架安装
npm i create-react-app -g
create-react-app jest-react
cd jest-react
npm install

// 把项目中的各种配置文件都弄出来
npm run eject

// 把package.json的jest部分挪出到新建的jest.config.js里面
"test":"node scripts/test.js" // 会运行src目录下所有的xxx.test.js
import React from 'react'
import ReactDOM from 'react-dom'
import Add from './App'
it('renders without crashing', ()=>{
	const div =document.createElement('div')
	const container=div.getElementByClassName('App')
	expect(container.length).toBe(2)
})

enzyme配置和使用

对react dom render进行包装,提供额外的方法供我们调用,能更灵活测试组件

// 安装
npm i enzyme enzyme-adapter-react-16 -D  // 适配16版本的适配器
// 配置
import React from 'react'
import Enzyme, {shallow} from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import Add from './App'
Enzyme.configure({adapter:new Adapter()])

it('test',()=>{
	// shallow浅渲染,只会渲染当层,不会渲染子组件(子组件用字符标记)
	const warpper = shallow(<App />)
	expect(wrapper.find('.app-container').length).toBe(1)
	expect(wrapper.find('.app-container').prop('title')).toBe('abc')
	// <div className='app-container' title='abc' data-test='container'></div>
	expect(wrapper.find('[data-test="container"]‘).prop('title')).toBe('abc') // 属性渲染器
	console.log(wrapper.debug())
})

注:集成测试用mount,单元测试用shallow

  1. jest-enzyme库
// 安装库 jest-enzyme 可以使代码更简洁
npm i jest-enzyme -D
"setupFilesAfterEnv":["./node_modules/jest-enzyme/lib/index.js"]

const container = wrapper.find'[data-test="container"]')
expect(container).toExist() 
expect(container).toHaveProp('title', 'abc') 
  1. 操作事件simulate
it('header组件 用户输入',()=>{
	const wrapper = shallow(<Header />)
	const inputElm = wrapper.find("[data-test='input']")
	inputElm.simulate('change', {
		target: { value: 'learn jest' }
	})
	expect(wrapper.state('value')).toEqual('learn jest')
	/* 为什么要重新获取elm,当数据发生变化的时候,老的inputElm还是之前的状态,
	所以必须重新获取elm
	const newInputElm = wrapper.find("[data-test='input']")
	expect(newInputElm.prop('value')).toBe('learn jest')
	*/
})
it('header组件 input回车,如果input无内容就没操作',()=>{
	const fn = jest.fn()
	const wrapper = shallow(<Header addUndoItem={fn} />)
	const inputElm = wrapper.find("[data-test='input']")
	const strInput ='learn jest'
	wrapper.setState({value: strInput})
	inputElm.simulate('keyup', {
		keyCode: 13
	})
	// expect(wrapper.state('value')).toEqual('learn jest')
	// expect(fn).not.toHaveBeenCalled() // 没有被调用过
	expect(fn).toHaveBeenCalled() 
	expect(fn).toHaveBeenLastCalledWith(strInput) 
	const newiInputElm = wrapper.find("[data-test='input']")
	expect(newiInputElm.prop('value')).toBe('')  // 回车后input value为空
})
//TodoList 组件中 return( <Header addUndoItem={this.addUndoItem.bind(this)}/> )
it('TodoList给Header传递一个方法 ',()=>{
	const wrapper = shallow(<TodoList />)
	const Header= wrapper.find("Header")
	expect(Header.prop('addUndoItem')).toBe(wrapper.instance().addUndoItem)
})

it('当Header回车时,undoList应该新增内容',()=>{
	const wrapper = shallow(<TodoList />)
	const Header= wrapper.find("Header")
	const addFunc= wrapper.prop("addUndoItem")
	addFunc('learn jest')
	expect(wrapper.state('undoList').length).toBe(1)
	expect(wrapper.state('undoList')[0]).toBe('learn jest')
})

  1. 生成快照
it('Header 渲染样式正常',()=>{
	const wrapper = shallow(<TodoList />)
	expect(wrapper).toMatchSnapshot()
})
  1. 提取通用代码
"setupFilesAfterEnv":["<rootDir>/src/utils/testSetup.js"]

// ./utils/testSetup.js  这里放常用方法或者初始化的类,每个测试用例文件就不用再加这些了
import Enzyme from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
Enzyme.configure({adapter:new Adapter()])

BDD(Behavior Driven Developmen)

TDD:先写测试再去开发(测试驱动开发)
BDD:用户的行为是什么样子就按用户的行为去开发,去编写测试用例(集成测试,多个组件测试,用户的行为涉及到各个组件的联动)(行为驱动开发)

不能使用shallow,要用mount

it(`
	1. header 输入框输入内容
	2. 点击回车
	3. 列表中展示用户输入的内容项
`, ()=>{
	const wrapper = mount(<TodoList />)
	
})

待续…

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: BDD (Behavior-Driven Development) 是一种软件开发方法,旨在通过对软件行为的描述来指导开发过程。它是测试驱动开发(TDD)的一种变体,旨在更加关注软件的实际行为,而不是细节实现。 在 BDD 中,开发人员与业务利益相关者(如客户或产品经理)合作,共同编写预期软件行为的描述。这些描述称为用例,通常以类似自然语言的方式书写。每个用例都包含了输入、期望的输出、和预期的结果。 JavaScript BDD 就是使用 JavaScript 编写 BDD 用例的方法。常用的 JavaScript BDD 框架有 Mocha 和 Jasmine。 Mocha 是一个流行的 JavaScript BDD 框架,提供了一个简单的测试接口,可以在多种 JavaScript 环境中使用。Jasmine 是另一个流行的 JavaScript BDD 框架,提供了一个简单的、可扩展的测试接口。 使用 JavaScript BDD 框架编写用例的流程通常如下: 1. 安装所需的框架(如 Mocha 或 Jasmine) 2. 编写一个或多个测试套件(test suite),每个测试套件包含一组相关的测试 3. 在每个测试套件中编写测试用例(test case) 4. 运行测试套件,并查看测试 ### 回答2: JavaScript BDD(行为驱动开发)是一种软件开发方法,旨在通过关注应用程序的行为和用户需求来开发高质量的JavaScript代码。 BDD的核心原则是将开发的重点从代码本身转移到应用程序的行为上。在JavaScript中,BDD的实践包括使用行为描述语言(例如Cucumber或Jasmine)来编写可读性强的测试用例。通过这些测试用例,开发者可以更清晰地定义函数或模块的预期行为,并确保它们在不同的情况下正确工作。 在JavaScript中,BDD的好处不仅仅体现在测试方面。由于BDD注重应用程序的行为和用户需求,它提供了一种更直观、可读性更强的代码编写方式。通过使用描述性的测试用例和行为描述语言,开发人员可以更好地理解应用程序的功能,并确保代码与预期行为保持一致。 此外,BDD还加强了开发团队内的沟通和协作。开发者通过共同编写并讨论测试用例,可以更好地理解应用程序的需求和行为。这种沟通和协作减少了误解和错误的可能性,并提高了团队的效率和代码质量。 总之,JavaScript BDD是一种通过关注应用程序的行为和用户需求来开发高质量代码的方法。它通过使用描述性的测试用例和行为描述语言,提供了一种更直观、可读性更强的代码编写方式。此外,BDD还加强了开发团队的沟通和协作,有助于提高团队的效率和代码质量。 ### 回答3: JavaScript BDD(行为驱动开发)是一种使用JavaScript编写自动化测试的开发方法。它结合了BDDTDD(测试驱动开发)的概念和方法。 BDD的核心思想是从业务需求和用户行为出发,将测试用例以自然语言的方式表达出来,然后根据这些用例来编写代码。JavaScript BDD可以使用一些专门的框架,如Jasmine、Mocha或Cucumber.js来实现。 在JavaScript BDD中,我们首先对应用程序的行为进行建模,然后将这些行为转化为测试用例。这些测试用例使用描述性的语法来描述期望的行为和结果。然后,我们使用JavaScript编写测试代码来验证这些期望。这种描述性的语法对于团队中的非开发人员也非常友好,因为他们可以更容易地理解和参与测试。 JavaScript BDD中的测试代码主要使用断言语句来比较实际结果与期望结果。这些测试代码可以在浏览器端或服务器端运行,以验证应用程序在各种情况下的行为是否符合预期。 通过使用JavaScript BDD,开发团队可以更准确地了解和满足用户需求,因为测试用例是直接基于用户行为进行设计的。此外,由于测试用例是以自然语言的形式编写,非开发人员更容易理解和验证这些测试用例。 总之,JavaScript BDD是一种结合了BDDTDD思想的开发方法,利用描述性的语法和自然语言来编写测试用例,以验证应用程序的行为是否符合预期。使用JavaScript BDD可以帮助开发团队更好地理解和满足用户需求,从而提高软件质量和开发效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值