国外怎么做直播网站,桐乡建设规划局网站,网站制作报价是否合法,网络营销方案步骤一、前端自动化测试需要测什么
1. 函数的执行逻辑#xff0c;对于给定的输入#xff0c;输出是否符合预期。
2. 用户行为的响应逻辑。
- 对于单元测试而言#xff0c;测试粒度较细#xff0c;需要测试内部状态的变更与相应函数是否成功被调用。
- 对于集成测试而言对于给定的输入输出是否符合预期。
2. 用户行为的响应逻辑。
- 对于单元测试而言测试粒度较细需要测试内部状态的变更与相应函数是否成功被调用。
- 对于集成测试而言测试粒度较粗一般测试ui展示上的变更文本内容改变、组件类别改变等。
3. 快照测试。对于不需要经常修改dom结构的组件我们会存储一个快照如果在后续的版本中修改了dom结构测试用例会不通过需要确认更新快照。 二、为什么需要自动化测试
你或许会疑惑如果我们做的是一些业务的开发而不是工具类函数的开发似乎手动测试就可以满足需求。对于用户行为的响应逻辑可以通过点击来测试dom结构的变更也可以通过肉眼观察。测试用例的代码可能甚至比业务代码量大那前端有必要耗时耗力的进行自动化测试吗
长期来看集成自动化测试是有必要的。
1. 有利于回归测试。在公司项目中产品是经常迭代的当我们修改了A功能就需要测试相关联的B功能不受影响。如果没有自动化测试每一次回归测试都需要手动进行且并不能保证你没纳入考虑范围的C功能是不受影响的。自动化测试有利于降低回归测试的成本并提高程序员的安全感。
2. 有利于代码重构。跟上一点类似我们需要保证重构前后的预期是一致的这时候我们就可以先对于老代码编写测试用例使测试用例能够全部通过。再重构业务代码如果重构后的代码也能通过全部的测试用例那么代码的可靠性是较高的。如果采用手动测试的方案那么你的执行流可能是重构A功能-测试A功能-重构B功能-测试A和B功能。因为重构前的代码架构通常会混乱一点所以为了确保后重构的功能不影响先重构的功能手动测试的工作量会越来越大。
3. 有利于代码优化。当测试用例通过后就可以放心的进行代码优化了省去了每次代码优化完手动测试的成本。
4. 前端开发与后端接口解耦。假设后端接口还未开发完成当我们和后端约定好数据结构后就可以模拟后端接口返回的数据并进行测试。当然我们也可以选择在业务代码里写死数据并进行手动测试但这就意味着后续需要修改业务代码或者选择用抓包工具模拟响应结果进行手动测试这种不需要修改业务代码但是需要权衡手动测试和自动化测试的成本。 三、TDD与BDD
测试驱动开发Test-Driven Development的流程如下
1. 根据要实现的功能编写测试用例测试用例不通过
2. 实现相关功能测试用例通过
3. 优化代码完成开发
由于测试用例不通过时会显示红色测试用例通过后会显示绿色所以测试驱动开发又称Red-Green-Development。 测试驱动开发的优点如下
1. 实现代码前先编写测试用例确保代码一定是易于测试的在开发视角的基础上扩展了测试视角代码的组织架构会更好。
2. 如果测试用例有误可能在实现功能前后均可以通过测试。由于我们先编写测试用例后开发如果测试用例在开发前就通过那么大概率测试用例是有问题的我们就可以及时发现修改。降低了编写出错误测试代码的可能性。
3. 自动化测试的通用优点。 行为驱动开发Behavior-Driven Development一般是测试驱动开发的自然延伸它的核心在于根据用户行为来设计测试用例对于是否需要在开发前编写测试用例没有强制要求。 四、单元测试与集成测试
单元测试的优点 测试粒度细代码覆盖率高运行速度快。
单元测试的缺点
1. 代码量大 。
2. 关注代码实现细节与业务代码耦合度高。
3. 每个单元的单元测试通过也无法保证集成后能正常运行。比如说A组件给B组件传入dataA组件测试传入的data结构为对象B组件测试接收到的data为数组两个组建的测试用例都能通过但是集成后运行就会由于数据结构不一致报错。
单元测试的适用场景工具库。 集成测试的优点
1. 测试粒度没那么细比所有单元的单元测试代码量总和小。
2. 不关注代码实现细节只关心展示给用户的结果业务代码耦合度较低。
3. 集成测试能确保单元能够协作运行通过集成测试通常来讲系统对于用户能够正常运行。
集成测试的缺点
1. 集成测试测试可能不如单元测试细致。
2. 集成测试需要运行多个组件测试速度会慢一些。
集成测试的适用场景业务系统。 五、具体实现
单元测试、集成测试、TDD、BDD之间要怎么集成其实没有标准答案而且BDD和TDD本身也不是对立的概念。只是BDD本身是以用户的“故事”为导向的这些故事通常涉及不止一个单元所以BDD通常和集成测试结合单元测试与TDD结合。
1. 单元测试与TDD
用react脚手架创建出项目后由于enzyme官方没有适配react17及以上版本需要将react版本降级为16。如果需要用react17及以上的版本可以用非官方的适配器或者改用react-testing-library。但是react-testing-library本身不关注代码实现细节而是以用户视角触发的所以我个人感觉不是很适合做单元测试。
npm install react16 react-dom16 --save
npm install enzyme enzyme-adapter-react-16 --save-dev 我们做一个简单的todoList项目当我们在输入框中输入内容按下回车后就会展现在下方。点击项尾的删除键可以进行删除。
我们将这个项目拆分为Header组件和UndoList组件。
我们在src目录下创建如下结构__tests__/unit目录下编写单元测试代码TodoList目录下编写业务代码。 因为采用TDD的模式开发所以先来编写测试代码。
环境准备
enzyme与react集成需要配置适配器由于这个配置需要在每个测试文件最开始引入所以我们可以将其抽离到一个单独的文件中然后对jest进行配置让jest在测试环境准备好后执行该文件。
react内部其实已经对jest进行了配置我们需要做的就是将其暴露出来。
// npm run eject的执行前提是没有未追踪的文件所以我们需要先初始化git仓库并提交
git init
git add .
git commit -m init resposity
npm run eject 在运行完npm run eject后我们可以发现package.json文件有新增的配置项Jest配置项中有一个属性是setupFilesAfterEnv。 jest: {// ...setupFilesAfterEnv: [rootDir/src/setupTests.js],// ...}, 可以看出setupTests文件会在测试环境准备好后执行所以我们只需要新增文件setupEnzyme.js修改jest配置项并将enzyme的适配器配置填入该文件即可。 jest: {// ...setupFilesAfterEnv: [rootDir/src/setupTests.js,rootDir/src/setupEnzyme.js],// ...},
// src/setupEnzyme.jsimport Enzyme from enzyme;
import Adapter from enzyme-adapter-react-16;Enzyme.configure({ adapter: new Adapter() });
由于jest本身不支持TextEncoder、TextDecoder、ReadableStream而在enzyme内部又会调用到相应的方法所以我们需要在import Enzyme前将方法挂载在global上。
// setupTests.jsimport { TextEncoder, TextDecoder, ReadableStream } from util;
global.TextEncoder TextEncoder;
global.TextDecoder TextDecoder;
global.ReadableStream ReadableStream; 测试用例编写
- Header
header组件的功能是点击回车键时能将数据传送给TodoList我们将header组件设计成受控组件即组件内的状态会根据用户输入实时更新。对功能进行拆解如下
1. 输入框的展示值为state.inputDatastate.inputData初始化为空。
2. input框输入内容时state.inputData随之改变。
3. 当输入框不为空时用户敲击回车后调用props.addUndoItemstate.inputData清空。
4. 当输入框为空时用户敲击回车后不调用props.addUndoItem.
4. 快照测试。
test(输入框的展示值为statestate.inputData初始化为空, () {const wrapper shallow(Header /);const input wrapper.find([data-test-idinput]);expect(input.prop(value)).toBe(wrapper.state(inputData));expect(wrapper.state(inputData)).toBe();
})
由于我们还未编写业务代码运行npx jest时测试用例是不通过的。 业务代码编写
由于enzyme只能追踪到类组件里的状态所以这里我们创建类组件。
如果你需要用函数组件可以参考Testing React Hook State Changes - DEV Community他的核心思路是相信react只要我们调用了setState方法传入了正确的参数就认为状态可以被正确的修改。通过mock setState方法来判断调用了函数并传入了正确的参数。
import React, { Component } from react;export default class Header extends Component {state {inputData: ,};render() {return (divinput data-test-idinput value{this.state.inputData} //div);}
}
再次运行测试测试通过。 然后是第2-4个测试用例及业务代码。
test(输入框输入字符时state.inputData随之改变, () {const wrapper shallow(Header /);const input wrapper.find([data-test-idinput]);const inputData hello world;input.simulate(change, { target: { value: inputData } });expect(wrapper.state(inputData)).toBe(inputData);
})
import React, { Component } from react;export default class Header extends Component {state {inputData: ,};render() {return (divinputdata-test-idinputvalue{this.state.inputData}onChange{(e) this.setState({ inputData: e.target.value })}//div);}
}
test(当输入框不为空时用户敲击回车后调用props.addUndoItemstate.inputData清空, () {const func jest.fn();const wrapper shallow(Header addUndoItem{func} /);const inputData hello world;wrapper.setState({ inputData });const input wrapper.find([data-test-idinput]);input.simulate(keyUp, { keyCode: 13 });expect(func).toHaveBeenCalled();expect(func).toHaveBeenLastCalledWith(inputData);expect(wrapper.state(inputData)).toBe();
})test(当输入框为空时用户敲击回车后调用props.addUndoItemstate.inputData清空, () {const func jest.fn();const wrapper shallow(Header addUndoItem{func} /);wrapper.setState({ inputData: });const input wrapper.find([data-test-idinput]);input.simulate(keyUp, { keyCode: 13 });expect(func).not.toHaveBeenCalled();
})
import React, { Component } from react;export default class Header extends Component {state {inputData: ,};handleKeyUp (e) {if (e.keyCode 13 this.state.inputData ! ) {this.props.addUndoItem(this.state.inputData);this.setState({ inputData: });}};render() {return (divinputdata-test-idinputvalue{this.state.inputData}onChange{(e) this.setState({ inputData: e.target.value })}onKeyUp{this.handleKeyUp}//div);}
}header组件的逻辑编写完成接下来我们补充完样式以后就可以进行快照测试了。
先引入一下header。
// index.js
import React from react;
import ReactDOM from react-dom;
import App from ./App;
ReactDOM.render(App /, document.getElementById(root))// App.jsx
import TodoList from ./container/TodoList;
export default function App() {return TodoList /
}// container/TodoList/index.jsx
import React, { Component } from react;
import Header from ./Header;
export default class index extends Component {render() {return (divHeader //div);}
}
运行npm run start页面展示如下。 我们对样式进行优化优化后页面展示如下。 // container/TodoList/style.css.header-wrapper {display: flex;align-items: center;justify-content: center;height: 100px;background-color: #e1d3d3;gap: 20px;
}.header-input {outline: none;line-height: 24px;width: 360px;border-radius: 5px;text-indent: 10px;
}.header-span {font-size: 24px;font-weight: bold;
}
// container/TodoList/Header.jsx
import React, { Component } from react;export default class Header extends Component {state {inputData: ,};handleKeyUp (e) {if (e.keyCode 13 this.state.inputData ! ) {this.props.addUndoItem(this.state.inputData);this.setState({ inputData: });}};render() {return (div classNameheader-wrapperspan classNameheader-spanTodoList/spaninputdata-test-idinputclassNameheader-inputvalue{this.state.inputData}placeholder请输入待办项onChange{(e) this.setState({ inputData: e.target.value })}onKeyUp{this.handleKeyUp}//div);}
}// container/TodoList/index.jsx
import ./style.css;
// ... 快照测试
test(快照测试, () {const wrapper shallow(Header /);expect(wrapper).toMatchSnapshot();
}) 运行测试用例后会生成__snapshot__文件夹后续修改样式/dom结构都会导致测试不通过。 剩下两个组件的流程不做赘述编写完代码如下。
// __tests__/TodoList.jsimport { shallow } from enzyme;
import TodoList from ../../index;let wrapper;
beforeEach(() {wrapper shallow(TodoList /);
})test(快照测试, () {expect(wrapper).toMatchSnapshot();
})test(state.undoList初始化为空, () {expect(wrapper.state(undoList)).toEqual([]);
})test(向header传入addUndoItem方法当该方法被调用时更新state.undoList, () {const header wrapper.find([data-test-idheader]);expect(header.prop(addUndoItem)).toBeTruthy();expect(header.prop(addUndoItem)).toEqual(wrapper.instance().addUndoItem);const prevUndoList [hello];wrapper.setState({ undoList: prevUndoList });const inputData world;wrapper.instance().addUndoItem(inputData);expect(wrapper.state(undoList)).toEqual([...prevUndoList, inputData]);
})test(向undoList组件传入list属性属性值为state.undoList, () {const undoList wrapper.find([data-test-idundo-list]);expect(undoList.prop(list)).toBeTruthy();expect(undoList.prop(list)).toEqual(wrapper.state(undoList));
})test(向undoList组件传入deleteUndoItem方法当该方法被调用时更新state.undoList, () {const undoListData [hello, world];wrapper.setState({ undoList: [...undoListData] });const undoList wrapper.find([data-test-idundo-list]);expect(undoList.prop(deleteUndoItem)).toBeTruthy();expect(undoList.prop(deleteUndoItem)).toEqual(wrapper.instance().deleteUndoItem);wrapper.instance().deleteUndoItem(0);expect(wrapper.state(undoList)).toEqual([undoListData[1]]);
})
// TodoList/index.js
import React, { Component } from react;
import Header from ./Header;
import ./style.css;
import UndoList from ./UndoList;export default class index extends Component {state {undoList: [],};addUndoItem (item) {this.setState({undoList: [...this.state.undoList, item],});};deleteUndoItem (index) {const newUndoList this.state.undoList;newUndoList.splice(index, 1);this.setState({ undoList: newUndoList });};render() {return (divHeader data-test-idheader addUndoItem{this.addUndoItem} /UndoListdata-test-idundo-listlist{this.state.undoList}deleteUndoItem{this.deleteUndoItem}//div);}
}// __tests__/Undolist.js
import { shallow } from enzyme;
import UndoList from ../../UndoList;test(快照测试, () {const wrapper shallow(UndoList list{[]} /);expect(wrapper).toMatchSnapshot();
})test(props.list为空时列表展示为空, () {const wrapper shallow(UndoList list{[]} /);// console.log(wrapper.find([data-test-idlist-item]));expect(wrapper.find([data-test-idlist-item]).length).toBe(0);
})test(props.list为不为空时列表展示对应项, () {const list [hello, world];const wrapper shallow(UndoList list{list} /);const listItem wrapper.find([data-test-idlist-item]);expect(listItem.length).toBe(2);expect(listItem.at(0).text()).toBe(hello);expect(listItem.at(1).text()).toBe(world);
})test(点击删除按钮时调用props.deleteUndoItem, () {const list [hello, world];const func jest.fn();const wrapper shallow(UndoList list{list} deleteUndoItem{func} /);const deleteBtn wrapper.find([data-test-iddelete-btn]);deleteBtn.at(0).simulate(click);expect(func).toHaveBeenCalled();expect(func).toHaveBeenLastCalledWith(0);
})// UndoList.jsx
import React, { Component } from react;export default class UndoList extends Component {render() {return (ul classNameundo-list-wrapper{this.props.list.map((item, index) {return (li key{index} classNameundo-list-itemdiv data-test-idlist-item{item}/divdivdata-test-iddelete-btnclassNameundo-delete-btnonClick{() this.props.deleteUndoItem(index)}-/div/li);})}/ul);}
}
页面展示效果如下 我们运行npx jest --coverage来看一下测试覆盖率。
运行完命令后会新生成coverage文件夹我们在浏览器打开index.html。 由于我们没给src/index.js和src/App.jsx编写测试用例所以第一条显示为0。但是对于我们编写了测试用例的TodoList组件可以看到测试的覆盖率是百分百所以TDD这种开发模式的代码覆盖率是非常高的。 2. 集成测试与BDD
可以看出在进行单元测试时测试代码量是比较大的如果单元内部的逻辑很复杂那么测试代码量还会大幅增加。
而且我们在单元测试中用了大量业务代码内的属性像state、props等这些其实对于用户来说是不可见的。那么我们可不可以站在用户视角以模拟用户行为的方式来进行黑盒测试呢答案是肯定的。
站在用户角度Todolist无非就干了三件事。
1. 待办项初始化为空。
2. 输入待办项后回车待办项会被展示在最下方。
3. 点击删除按钮对应的待办项被删除。 根据以上的用户故事我们编写对应的测试代码。
import { mount } from enzyme;
import TodoList from ../../index;let wrapper;// 对于集成测试而言我们需要渲染子组件所以调用mount方法
// 对于mount方法元素会被真正挂载在页面上所以如果我们在两个测试用例里面分别创建了wrapper
// 且每个wrapper中有一个undoListItem
// 那么在不调用卸载方法的情况下页面上会存在两个undoListItem
// 所以在集成测试里我只创建了一次wrapper
beforeAll(() {wrapper mount(TodoList /);
});test(1. 用户进入网站2. 待办项显示为空, () {const undoListItem wrapper.find([data-test-idlist-item]);expect(undoListItem.length).toBe(0);
})test(1. 用户输入待办项2. 用户敲击回车3. 待办项展示在下方, () {const input wrapper.find([data-test-idinput]);const inputData hello;input.simulate(change, { target: { value: inputData } });input.simulate(keyUp, { keyCode: 13 });const undoListItem wrapper.find([data-test-idlist-item])expect(undoListItem.length).toBe(1);expect(undoListItem.text()).toBe(inputData);
});test(1. 用户输入待办项2. 用户敲击回车3. 在原待办项下方新增待办项, () {const input wrapper.find([data-test-idinput]);const inputData world;input.simulate(change, { target: { value: inputData } });input.simulate(keyUp, { keyCode: 13 });const undoListItem wrapper.find([data-test-idlist-item]);expect(undoListItem.length).toBe(2);expect(undoListItem.at(1).text()).toBe(inputData);
});test(1. 用户点击第一项的删除按钮2. 第一项被删除, () {const deleteBtn wrapper.find([data-test-iddelete-btn]);deleteBtn.at(0).simulate(click);const undoListItem wrapper.find([data-test-idlist-item]);expect(undoListItem.length).toBe(1);expect(undoListItem.text()).toBe(world);
})test(1. 用户点击第一项的删除按钮2. 第一项被删除, () {const deleteBtn wrapper.find([data-test-iddelete-btn]);deleteBtn.at(0).simulate(click);const undoListItem wrapper.find([data-test-idlist-item]);expect(undoListItem.length).toBe(0);
})
因为我们已经实现了对应的业务代码所以测试用例均可以正常通过。
可以看出相比较对于一个个单元进行单元测试整体编写集成测试的代码量是会更少的。如果后续我们修改了state里的数据结构或者是props的属性名只要最终展现在页面上的结果不变那么集成测试都可以通过。