作者 | 张霄翀
目前创新互联建站已为上千多家的企业提供了网站建设、域名、虚拟主机、网站托管运营、企业网站设计、龙江网站维护等服务,公司将坚持客户导向、应用为本的策略,正道将秉承"和谐、参与、激情"的文化,与客户和合作伙伴齐心协力一起成长,共同发展。
我曾经在好几个项目里都近乎完整参与过补齐前端测试的工作,也收集到不同项目的同事很多关于前端测试的困惑和痛点,这其中大部分都很相似,我也感同身受,在这篇文章里,我会针对大家和自己常遇到的痛点分享一些自己的经验,如果你也有如下相似的困扰,那希望这篇文章能对你有些帮助~
常见问题(排名不分先后):
在分享问题的相关经验之前,我们先来梳理一下前端测试体系~
这其实跟所有测试的重要性是一样的,大家有这么多的痛点也是因为知道覆盖全面的测试可以对代码质量更有保证,让我们更有信心地去重构代码,也能帮助我们更方便地了解现有的功能细节,甚至是一些极端的边界情况。而且在大家合作开发项目代码的过程中,测试可以帮助我们更早地发现错误,减少时间成本,提高交付效率。
这两个常见的测试方法论在这里简单介绍一下,就不大篇幅展开了。TDD - (Test-Driven Development 测试驱动开发)简单地说就是先根据需求写测试用例,然后实现代码,通过后再接着写下一个测试和实现,循环直到全部功能和重构完成。基本思路就是通过测试来推动整个开发的进行。BDD - (Behavior Driven Development 行为驱动开发) 其实可以看做是TDD的一个分支。简单地说就是先从外部定义业务行为,也就是测试用例,然后由外入内的实现这些行为,最后得到的测试用例也是相应业务行为的验收标准。
在这里借一下前端大牛Kent C. Dodds的奖杯分层法来引出常见的分类:
(图片出处:https://kentcdodds.com/blog/static-vs-unit-vs-integration-vs-e2e-tests)
端到端测试一般会运行在完整的应用系统上(包括前端和后端),包含用户完整的使用场景,比如打开浏览器,从注册或登录开始,在页面内导航,完成系统提供的功能,最后登出。
有时,我们也会在这里引入可视化用户界面测试,即一种通过像素级比较屏幕截屏来验证页面显示是否正确的测试。目的是确保界面在不同设备、浏览器、分辨率和操作系统下与预期的样式一致。可以设置一定的偏差容忍值。这一层的测试成本较高,所以通常重心会放在确保主流程的功能正常上。常用工具:Cypress、Playwright、Puppeteer、TestCafe、Nightwatch (下载量对比)
集成测试 Integration Test
集成测试主要是测试当单元模块组合到一起之后是否功能正常。在不同的测试上下文下可能有不同的定义,在前端测试这里通常指测试集成多个单元组件到一起的组件。
单元测试就是对没有依赖或依赖都被mock掉了的测试单元的测试。在前端代码里,它可能是:
主要是指利用一些代码规范工具(Lint Tool)来及时捕获代码中潜在的语句错误,统一代码格式等。这里就不展开了。常见工具和实践有:
还是这张图,我标记了一下:
在奖杯的形状上每一层占的面积代表了应该投入的重心比例。
这里集成测试的比重比单元测试大是因为集成测试可以在成本很高的e2e测试和离最终用户行为较远的单元测试之间取的一个平衡,它可以写的很接近最终用户的行为,成本又相对的没那么高,属于性价比很高的一部分。
所以集成测试有一些原则:
对于单元测试来说:
测试启动工具负责将测试运行在Node.js或浏览器环境。形式可能是CLI或UI,并结合一定的配置。常见工具有:Jest / Karma / Jasmine / Cypress / TestCafe 等。
测试结构工具提供一些方法和结构将测试组织的更好,拥有更好的可读性和可扩展性。如今,测试结构通常以BDD形式来组织。测试结构如下方Jest例子:
// Jest test structure
describe('calculator', () => {
// 第一层级: 标明测试的模块名称
beforeEach(() => {
// 每个测试之前都会跑,可以统一添加一些mock等
})
afterEach(() => {
// 每个测试之后都会跑,可以统一添加一些清理功能等
})
describe('add', () => {
// 第二层级: 标明测试的模块功能分组
test('should add two numbers', () => {
// 实际的描述业务需求的测试
...
})
})
})
常见工具有:Jest / Mocha / Cucumber / Jasmine / Cypress / TestCafe 等。
断言库会提供一系列的方法来帮助验证测试的结果是否符合预期。如下方的例子:
// Jest expect (popular)
expect(foo).toEqual('bar')
expect(foo).not.toBeNull()
// Chai expect
expect(foo).to.equal('bar')
expect(foo).to.not.be.null
常见工具有:Jest / Chai / Assert / TestCafe 等。
有的时候我们在测试的时候需要隔离一些代码,模拟一些返回值,或监控一些行为的调用次数和参数,比如网络请求的返回值,一些浏览器提供的功能,时间计时等,Mock工具会帮助我们更容易的去完成这些功能。
常见工具有:Sinon / Jest (spyOn, mock, useFakeTimers…) 等。
快照测试对于UI组件的渲染测试十分有效。原理是第一次运行时生成一张快照文件,需要开发人员确认快照的正确性,之后每一次运行测试都会生成一张快照并与之前的快照做比较,如果不匹配,则测试失败。这时如果新的快照确实是更新代码后的正确内容,则可以更新之前保存的快照。(这里的快照通常都是框架渲染器生成的序列化后的字符串,而不是真实的图片,这样的测试效率比较高)。
这里可以参考Jest官方的用例。
常见工具有:Jest / Ava / Cypress
测试覆盖率工具可以产出测试覆盖率报告,通常会包含行、分支、函数、语句等各个维度的代码覆盖率,还可以生成可视化的html报告来可视化代码覆盖率。如以下的Jest内置的代码覆盖率报告:
(图片出处:https://jestjs.io/)
常见工具有:Jest内置 / Istanbul。
上面在测试分层里介绍过的。
也在上面的测试分层里介绍过。通常会和e2e测试工具组合在一起使用,一般主流的e2e测试工具也会有对应的库去进行可视化用户界面测试。
不同的前端框架还会有一些自带的或推荐的测试库,比如:
基于上面的分类,大家可能发现几乎哪哪都有Jest,这类大而全的前端测试工具我们也可以称为前端测试框架。
常见的有:
最后附上一张stateOfJS网站2021年的测试库满意度图表供大家参考 :
(图片出处:https://2021.stateofjs.com/en-US/libraries/testing/)
终于回到最开始的问题了,分享一下我的经验和通常的解决办法:
前端测试感觉写起来很复杂,会花很多时间,甚至经常是业务代码时间的好几倍,这个问题可以分成三部分来下手:
可以根据刚才的测试策略部分,结合自己项目的实际情况,调整一下在不同的测试层分配的重心,定一下自己项目每个层级的测试粒度,这样才能在保证交付的前提下达到测试信心值收益的最大化。
(1) 抽取公共的部分,使具体的测试文件简洁
(2) 统一测试规范,有优化及时重构所有测试,这样大家可以放心的参考已有测试,不会有多种写法影响可读性
// testUtils.js
export const flushPromises = (interval = 0) => {
return new Promise((resolve) => {
setTimeout(resolve, interval);
});
};
// example.test.js
test('should show ...', async () => {
//render component
await flushPromises();
//verify component
});
通常问这个问题背后隐藏的问题是前端很难先写测试,再写实现。确实我也有同感,如果是一些util/helper方法是可以很容易的遵循TDD的步骤的,但当涉及页面结构和样式的时候,很难在写测试的时候就想清楚页面到底有哪些具体的元素,用到哪些需要mock的模块。
所以在测试UI组件时,我通常会使用BDD的方式,具体步骤是:
// Jest
describe('todo component', () => {
test('should show todo list', () => {
// Snapshot test
const tree = renderer.create().toJSON();
expect(tree).toMatchSnapshot();
})
test.skip('should add todo when click add and input todo content', () => {
})
test.skip('should remove todo when click delete icon of todo item', () => {
})
当然随着前端代码写的越来越熟练,为了提升效率,有时会简化步骤,等一个小功能的组件都重构完了,样式调好了,所有的子组件都抽完了,再根据每个组件的props和交互的点批量加测试,最后用测试覆盖率来验证是否都覆盖到了,保证自己新写的组件都尽可能是100%的覆盖率。
这个是我也很头疼的问题,有的时候一些第三方组件因为要实现一些复杂的效果,会使用不一样的方式去监听事件。
比如我们有一个Vue项目上用到了element-ui的select组件,这个组件可以通过:remote-method 属性开启异步发请求加载选项的功能,测试里想模拟异步拿到选项后并选择某选项,就需要想办法触发它的@change 事件,通常一条await fireEvent.update(input, 'S'); 就搞定了,但这个怎么都不生效,仔细的查看它的实现才发现需要这么一串操作才能触发到@change 事件。
const input = getByPlaceholderText('Please input to search');
await fireEvent.click(input);
await fireEvent.keyUp(input, { key: 'A', code: 'KeyA' });
await fireEvent.update(input, 'A');
await flushPromises(500); // 这个方法上面有介绍,的作用是让异步的代码返回结果,并且等待500ms,因为源码有500ms的等待,这里就也需要等待
await fireEvent.click(getByText('Apple'));
这里我总结的经验就是:
这个可以结合使用的测试工具去搜索,一般都会有很多现成的解决方案,在这里举两个例子:
Mock navigator.userAgent::
// jest.setup.js
Object.defineProperty(
global.navigator,
'userAgent',
((value) => ({
get() { return value; },
set(v) { value = v; },
}))(global.navigator['userAgent']),
);
// example.test.js
test('should show popup in Safari', () => {
global.navigator.userAgent = 'user agent of Safari ...';
// render and verify something
});
Mock window.open:
//jest.setup.js
Object.defineProperty(
window,
'open',
((value) => ({
get() { return value; },
set(v) { value = v; },
}))(window.open),
);
// example.test.js
test('should ...', () => {
window.open = jest.fn();
// render something
expect(window.open).toBeCalledWith('xxx', '_blank');
});
上面有介绍,可以将公共的部分抽取出去,又能减少代码重复,又能提升写测试的效率,比如准备数据的部分可以抽成公共的fixture文件,提供方法生成默认的数据,也可以通过参数去覆盖修改部分数据,达到定制化的目的:
export const generateUser = (user = {}) => {
return {
id: 1,
firstName: 'San',
lastName: 'Zhang',
email: 'sanzhang@test.com',
...user,
};
};
测试里的报错通常都很有价值,需要重视。这里面的错误有可能是:
虽然有的时候也会有一些由于第三方库的原因引起的无法修复又没有影响的log,可以忽略,但测试里大部分警告Log其实都是可以修复的,甚至在修复后可能得到意想不到的受益,比如发现真正业务代码的问题,测试不再随机挂了,测试运行性能提升了等等。
对于前端测试,我觉得重心不是机械的去追求测试覆盖率,而是尽可能的在成本和信心值中间找到一个平衡,应用一些好的实践去降低写测试的成本,提升写测试带来的回报,让大家对于项目质量越来越有信心。
分享题目:前端测试体系和优秀实践
标题网址:http://www.shufengxianlan.com/qtweb/news21/195421.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联