作者|张小冲前言我几乎完全参与了几个项目的前端测试的完成,也从不同项目的同事那里收集了很多关于前端测试的困惑和痛点。他们中的大多数人都非常相似,我也有同感。在这篇文章中,我将针对你我经常遇到的痛点,分享一些自己的经验。如果您也有以下类似问题,希望本文能对您有所帮助。求助~常见问题(排名不分先后):前端测试感觉写起来很复杂,会花很多时间,甚至经常是业务代码的好几倍。前端测试TDD怎么样?在测试一些第三方UI控件的时候,模拟与它们的交互是特别困难的。有些东西不知道怎么mock,比如时间,浏览器全局变量(window。代码很晚,翻了半天,不好定位,跑测试的时候会有很多Error或者WarnLog,貌似不影响测试通过,修复起来也比较费时间。在分享问题的相关经验之前,先梳理一下前端测试系统吧~前端测试系统的重要性前端测试其实和所有测试的重要性是一样的,大家都有那么多痛点,因为知道全面覆盖测试更能保证代码质量,让我们重构代码更有信心,也帮助我们更容易理解现有的功能细节,甚至一些极端的边缘情况。并且在大家合作开发项目代码的过程中,测试可以帮助我们更早发现错误,减少时间成本,改进交付效率.F前端测试方法论(TDDvs.BDD)这里简单介绍这两种常见的测试方法论,就不赘述了。TDD-(Test-DrivenDevelopment测试驱动开发)简单来说就是先根据需求编写测试用例,然后实现代码,通过后再编写下一个测试和实现,循环直到所有功能和重构完成。基本思想是通过测试来驱动整个开发。BDD——(BehaviorDrivenDevelopment行为驱动开发)其实可以看作是TDD的一个分支。简单的说,就是从外部定义业务行为,即测试用例,然后由外向内实现这些行为,最终得到的测试用例也是相应业务行为的验收标准。这里前端测试的分层借鉴了前端大牛KentC.Dodds的奖杯分层方法,引出常见的分类:(图片来源:https://kentcdodds.com/blog/static-vs-unit-vs-integration-vs-e2e-tests)EndtoEndTestEndtoEndTest一般运行在一个完整的应用系统上(包括前端和后端),包括用户完整的使用场景,比如打开浏览器,从注册或登录开始,在页面内导航,完成系统提供的功能,最后退出。有时,我们还会在这里介绍可视化用户界面测试,这是一种通过比较像素级别的屏幕截图来验证页面是否正确显示的测试。目的是保证界面在不同的设备、浏览器、分辨率和操作系统下与预期的风格一致。可以设置一定的偏差容差值。这一层的测试成本很高,所以重点通常放在确保主进程的功能上。常用工具:Cypress、Playwright、Puppeteer、TestCafe、Nightwatch(下载量对比)集成测试集成测试主要是测试单元模块组合在一起后功能是否正常。在不同的测试上下文中可能有不同的定义。在前端测试中,通常是指将多个单元组件集成在一起的测试组件。单元测试单元测试单元测试是对没有依赖关系或被模拟的依赖关系的测试单元的测试。在前端代码中,可能是:没有依赖或者被mock出来的依赖的Unit组件Utils/Helpers等公有方法集合测试等功能代码ReactHook/Selector等公有方法测试等辅助组件功能静态代码测试StaticTest主要是指利用一些代码规范工具(LintTool)及时捕捉代码中潜在的语句错误,统一代码格式等,这里不展开。常用的工具和做法包括:Eslint+Prettier代码规范和统一风格husky+lint-staged(gitHooks工具)可以在提交和推送前自动扫描代码,防止非标准代码进入代码库,也可以设置运行push之前有一次前端测试,前端测试策略还是这张图,我标注了:成本越高,得到反馈的速度越慢,但是级别越高,越接近终端用户的行为,越能发现真问题,越能发现真问题。给予的信心更多。奖杯形状中每一层所占的面积代表了应投入的重心比例。这里的集成测试比单元测试的比例要大一些,因为集成测试可以在昂贵的端到端测试和远离终端用户行为的单元测试之间取得平衡,并且可以写得非常接近终端用户行为,成本更低相对来说没有那么高,属于高性价比的一部分。因此,集成测试有一些原则:根据每个页面的复杂程度,决定是只做一个整页集成测试,还是分成几个大块进行集成测试,但一旦使用作为集成测试,需要尽量减少mock依赖,尽量渲染所有的子组件来测试用户看到的和交互的,而不是背后的实现,否则会远离最终用户的行为并且降低置信度值,随着代码的重构,测试也需要经常修改。比如Enzyme可以在组件中提供method、props、state等单独测试,但是这里的测试并不接近真实用户的交互,很容易因为重构而破坏测试。更好的方法是在道具和状态更改或交互更改后真正测试页面更改。准备尽可能丰富和接近真实数据的测试数据(应替换用户敏感信息)。越接近真实数据,越能覆盖真实问题。对于核心的业务行为,应该侧重于测试。对于单元测试:UI组件类测试:由于集成测试覆盖,你可以简单地测试不同道具的渲染。如果有一些特殊的数据是集成测试覆盖不了的。对于交互行为,可以测试非UI组件类的测试:通常会覆盖一些复杂的业务逻辑,需要综合测试不同的分支条件。前端测试工具分类Node.js或浏览器环境。形式可能是CLI或UI,结合某些配置。常用工具包括:Jest/Karma/Jasmine/Cypress/TestCafe等测试结构工具(StructureProviders)测试结构工具提供一些方法和结构来测试更好的组织性、更好的可读性和可扩展性。如今,测试结构通常以BDD形式组织。测试结构如下Jestexample://Jestteststructuredescribe('calculator',()=>{//第一层:表示测试的模块名beforeEach(()=>{//每个测试都会先运行,可以统一添加一些mocks等})afterEach(()=>{//每次测试后都会运行,可以添加一些清理函数等})describe('add',()=>{//第二层:标注测试模块功能分组test('shouldaddtwonumbers',()=>{//实际测试描述业务需求...})})})常用工具包括:Jest/Mocha/Cucumber/Jasmine/Cypress/TestCafe等AssertionFunctions断言库提供了一系列的方法来帮助验证测试结果是否符合预期。例如://Jestexpect(popular)expect(foo).toEqual('bar')expect(foo).not.toBeNull()//Chaiexpectexpect(foo).to.equal('bar')expect(foo.to.not.be.null常用工具有:Jest/Chai/Assert/TestCafe等Mock工具有时候我们需要在测试的时候隔离一些代码,模拟一些返回值,或者监控一些调用的次数和参数行为,比如网络请求的返回值,一些浏览器提供的功能,时间计时等,Mock工具会帮助我们更轻松的完成这些功能。常用工具有:Sinon/Jest(spyOn、mock、useFakeTimers…)等。快照测试工具(SnapshotComparison)快照测试对于UI组件的渲染测试非常有效。原理是在第一次运行时生成快照文件,开发者需要确认快照的正确性。之后,每次运行测试时,都会生成一个快照,并与之前的快照进行比较。如果不匹配,则测试将失败。此时,如果更新代码后新的快照确实是正确的内容,则可以更新之前保存的快照。(这里的截图一般是帧渲染器生成的序列化字符串,而不是真实的图片,所以测试效率比较高)。这里可以参考Jest官方用例。常用工具有:Jest/Ava/Cypress测试覆盖率工具(TestCoverage)测试覆盖率工具可以生成测试覆盖率报告,通常包括行、分支、函数、语句等各种维度的代码覆盖率,也可以生成Visual用于可视化代码覆盖率的html报告。比如下面的Jest内置代码覆盖率报告:(图片来源:https://jestjs.io/)常用工具有:Jestbuilt-in/Istanbul。上面测试层介绍了E2E测试工具(EndtoEndTest)。上面的测试层也介绍了可视化用户界面测试(VisualRegression)。通常与端到端测试工具结合使用,一般主流的端到端测试工具也有相应的可视化用户界面测试库。前端框架专属测试库不同的前端框架也有一些内置或推荐的测试库,例如:React:React官方测试工具/测试库-React(推荐)/Enzyme(基于上述测试策略),比较推荐ReactTestingLibrary,Enzyme暴露了太多内部元素用于测试,虽然一时方便,但离用户行为距离较远,后续修改频率比较高,性价比较低。)Vue:VueOfficialTestUtils/TestingLibrary-VueAngular:Angular内置测试框架(Jasmine)/TestingLibrary-Angular前端测试框架根据以上分类,你可能会发现Jest几乎无处不在。这种大而全的前端测试工具也可以称为前端测试框架。常见的有:Jest:强烈推荐,测试所需的几乎所有工具,活跃的社区,丰富的在线资源,也是React官方推荐的测试框架。Mocha:虽然功能也很丰富,但是没有断言库、测试覆盖率工具和Mock工具,需要结合其他第三方库使用Jasmine:它是老派工具,功能没有Jest丰富,下载量逐年下降。://2021.stateofjs.com/en-US/libraries/testing/)前端测试的常见问题终于回到了最初的问题。分享一下我的经验和通常的解决方法:前端测试感觉写起来很复杂,会花很多时间,甚至往往是业务代码时间的好几倍,这个问题可以分为三个部分来着手:优化测试strategy可以根据刚才的teststrategy部分进行调整,结合自己项目的实际情况,在不同的testlayer中分布的重点是确定自己项目各个level的测试粒度,从而最大化在保证交付的前提下测试置信度值的好处。提高编写测试效率(1)提取通用部分,使具体测试文件简洁。准备数据的fixture库可以方便的生成想要的存储数据或者请求返回数据。publicrender方法可以支持自定义store和stubsubcomponents和mockframework全局方法等公共第三方UI组件交互方法,可以轻松触发第三方控件的事件,无需关心publicapimock方法的实现细节。可以在测试文件中轻松mock,无需关心api细节(2)统一测试规范,及时优化重构所有测试,让大家放心参考现有测试,不会出现多种写法影响可读性提高运行测试的效率异步请求返回通常会给setTimeout一个等待时间。大多数情况下0就可以达到目的,除非逻辑真的需要等待一定的时间。如果默认值设置比较大,每次测试都会延迟。有的时候,加起来对测试运行性能影响很大//testUtils.jsexportconstflushPromises=(interval=0)=>{returnnewPromise((resolve)=>{setTimeout(resolve,interval);});};//example.test.jstest('shouldshow...',async()=>{//渲染组件awaitflushPromises();//验证组件});前端测试如何TDD通常会在后面问这个问题隐藏的问题是前端很难先写测试,再写实现。确实,我也有同感。如果是一些util/helper方法,很容易按照TDD的步骤进行,但是涉及到页面结构和样式,写测试的时候很难想清楚页面的具体元素。,哪些模块需要被模拟。所以在测试UI组件时,我通常使用BDD方式。具体步骤是:创建一个组件文件,渲染返回空创建一个测试文件,先写一个snapshot测试,测试通过,生成一个snapshot文件然后根据这个页面mockup写测试用例不容易对于已知的交互。通常,此时不容易编写和实现。首先,编写所有的测试用例。首先跳过测试。ESlint可以设置跳过测试,用warn显示,这样方便完成//Jestdescribe('todocomponent',()=>{test('shouldshowtodolist',()=>{//Snapshottestconsttree=renderer.create(
