当前位置: 首页 > Web前端 > JavaScript

前端单元测试-jest入门练习

时间:2023-03-26 22:12:58 JavaScript

前言单元测试用于测试程序中的一小块功能,比如一个函数或者一个类。可以显着提高项目的代码质量,减少bug出现的频率,也方便代码的维护。话虽如此,但大多数人都不愿意编写单个测试。.不过没关系,多学点东西也不是坏事。背景就不多介绍了,主要是练习。在这个项目中,我使用的框架是Jest。用法比较简单,这里简单介绍一下:什么是单元测试?单元测试(UnitTesting),也称模块测试,是对程序模块(软件设计的最小单元)正确性的测试。TDD:测试驱动开发首先编写测试用例,在测试用例的指导下完善功能。当编写好测试用例并通过测试后,相应的功能就完成了。示例包括函数和组件库。但是通常当代码发生变化时,测试用例应该相应地进行调整。BDD:行为驱动开发测试用例模拟用户的操作行为。通常,业务代码开发完成后,以用户操作为导向编写测试代码。当测试用例跑通后,就可以认为系统的整体流程已经很顺利了。BDD模式适用于正常的业务代码开发,因为业务需求可能会经常变化,但操作流程可能不会变。当业务代码发生变化时,可以利用原有的测试用例继续运行代码,节省开发时间。TDD负责方法类和独立组件的测试。BDD负责整体业务模块的测试。为什么需要单次测试?状态:缺乏意识:没有单测意识,很多代码没有单测缺乏设计:模块缺乏设计,相互耦合,难以编写单测难以测试:升级公共API,手动测试验证难以测试未完成:一些代码分支多,手动当测试无法覆盖所有分支时,如何衡量单个测试的完善程度?为了衡量单元测试的完善程度,用覆盖率来衡量单元测试的显着性。能力建设:有开发经验的开发者基本可以写出单元测试。即使没有,也可以通过训练很快达到。从学习曲线的角度来看,单元测试很容易上手。提高效率:通过mock数据,可以及早发现问题,越早发现bug,越少造成浪费。追求卓越:单元测试可以作为一种设计工具,帮助开发人员思考代码结构的设计,使代码更利于测试。更全面的测试:可以覆盖QA测试无法覆盖的情况,比如各种if分支,异常处理等。更有信心:在升级一个公共API时,如果所有依赖这个API的代码单元测试都能通过,那么我们对这次代码升级就更有信心了。概念介绍到此结束,接下来就是相关实践1.Assertion断言库是Jest中使用最多的,我们可以用它来测试目标函数的输出是否与预期一致。test("测试2+2=4",()=>{expect(sum(2,2)).toBe(4)})test("测试函数返回的对象为{name:'zhi'}",()=>{expect(getName()).toEqual({name:"zhi"})})更多信息请参考官方文档。2、编写异步代码测试通常有两种方式:回调函数返回promise在测试异步代码时,返回的数据通常是不确定的,所以我们只需要测试异步代码是否正常返回数据即可。对于回调函数,如果像同步函数那样测试,是没有办法得到正确的断言结果的exportconstfuncA=(callback:(data:number)=>void):void=>{>{callback(1);},1000);};test('funcA',()=>{funcA((data)=>expect(data).toEqual(2));});即便如此,我们的单测也能通过。这是因为jest运行funcA后直接结束,并没有等待setTimeout的回调,自然不会执行expect断言。正确的方式是传入一个done参数:test('funcA',(done)=>{funcA((data)=>{expect(data).toEqual(2);done();});});callback执行完后,显式告诉jest异步函数执行完毕,jest会等到done()执行完才结束,这样才能得到预期的结果。对于promise,只需要在测试用例结束时返回Promise即可。exportconstfuncB=():Promise=>{returnnewPromise((resolve)=>{setTimeout(()=>{resolve(1);},1000);});};test('funcB',()=>{returnfuncB().then((data)=>expect(data).toEqual(1));});//也可以使用awaittest('funcB',async()=>{constdata=awaitfuncB();expect(data).toEqual(1);});对于抛出异常的承诺,您需要使用.catch方法。这里有一个警告。添加expect.assertions以验证是否调用了一定数量的断言。否则,一个fulfilledPromise不会让测试用例失败。test('获取失败并出现错误',()=>{expect.assertionsassertions(1);returnfetchData().catch(e=>expect(e).toMatch('error'));});3.Mock如果我们需要测试回调函数是否执行,可以使用mock。这里想结合实践来介绍一下mock的用法。场景1:一个reacthooks使用react-router的useLocation。我们想模拟这个而不调用import{renderHook,act}from'@testing-library/react-hooks';letmockLocation=jest.fn(()=>{return{search:"test"}})jest.mock("react-router-dom",()=>{return{useLocation:()=>mockLocation()}})//使用测试("useTabChange",()=>{const{result,rerender}=renderHook(()=>useTabChange());expect(mockLocation).toBeCalled()})我们甚至可以在任何方法中模拟该函数以返回示例2:业务中使用了'opa-fe-base'库,我们需要从'@testing-library/react-hooks'模拟返回导入{renderHook,act};让mockFn=jest.fn()jest.mock(“opa-fe-base”,()=>({storeAPI:{getEntity:()=>mockFn()}}))从'../useCountry'describe("useCountry",()=>{test("useCountry返回SG",()=>{const{结果,重新渲染}=renderHook(()=>useCountry())expect(result.current).toBe('SG')mockFn=jest.fn(()=>{return{country:"ID"}})渲染()act(()=>{expect(result.current).toBe('ID')})})})从上面的例子可以看出,我们模拟了'opa-fe-base'库,返回了一个storeAPI对象,对象内部还有一个mock方法。我们可以通过改变mock方法的返回值来判断返回值是否符合预期。注意:需要测试的函数import是写在我们mock下的。最近在写单机测试的时候,发现可以直接使用jest.fn来模拟window.URL.createObjectURL事件。但是文档的事件没有模拟成功。查询文档后发现可以使用jest.spyOn。场景3:通过jest.spyOn模拟文档事件{window.URL.createObjectURL=jest.fn((blob)=>{returnblob})window.URL.revokeObjectURL=mockRevoke;})test("downloadBlobFilemustbecalled",()=>{下载)Bl文件{};expect(spyFn).toBeCalled()expect(mockRevoke).toBeCalled();})test("下载createObjectUrl使用",()=>{downloadBlobFile("test");expect(window.URL.createObjectURL.mock.calls[0][0]).toEqual('test')})})4.快照帮助我们确保在维护代码的同时不对组件的UI进行更改。expect(componentinstance).toMatchSnapshot()在第一次执行的时候会生成一个snapshot,然后比较每个snapshot是否相同。5.推荐一个vscodejest插件JestRunner这个插件可以在我们的测试文件中渲染按钮,帮助我们运行或者调试一个测试或者单独描述。无需全局运行。提高效率。最后,单测写的是有意义的,但是经常受限于工作中的业务,没有机会写出来,也可以做到,好好珍惜~~