前端项目中写单元测试其实很简单
、关于自动化测试1、测试分类自动化测试类型常分为以下三种各有优缺点单元测试Unit Test)对项目中低耦合的工具类库和公共子组件进行测试较为简单能在一定程度上保障代码质量集成测试Integration Test对于耦合度较高的函数/组件对外暴露的接口进行测试能较大程度保障产品质量但开发成本高UI测试UI Test前端中UI变动大适合人工检查了解测试术语1、TDD测试驱动开发2、BDD行为驱动开发3、测试覆盖率4、快照测试5、模拟函数6、断言2、单元测试 框架Jest是一个广受欢迎的单元测试框架简单易用功能强大。Vitest它由 Vue / Vite 团队成员开发和维护在 Vite 的项目集成它会非常简单而且速度非常快。Mocha一个灵活的测试框架需要各种插件来配合使用。Karma能在真实的浏览器中测试可配置其他单元测试框架Jasmine功能全面的测试框架相对复杂、不够灵活测试框架太多且各有优势大多数写法相差不多。我们这里选择Jest来分享其他测试框架可以自行了解。3、单元测试适用的测试对象有哪些1、常见工具类函数2、公共子组件3、接口请求数据二、给项目 配置Jest1、安装yarn add -D jest或npm install jest -D2、配置(非必需)如果你想获得更多的jest配置可以增加配置文件。比如项目中的Jest默认不显示测试覆盖率和测试报告等想要支持就需要我们将Jest的配置文件暴露出来只需要执行yarn test --init或npx jest --init然后根据需求选择对应的配置最后会在根目录下生成jest.config.js文件根据提示选择即可这里我们选择JsDom环境需要代码测试覆盖率报告自动清除每个单元测试之间的模拟调用和实例。执行完成后发现在项目根目录下多了一个jest.config.js文件里面包含了各种配置说明module.exports { // 是否显示覆盖率报告 collectCoverage: true, // 告诉 jest 文件测试要求的阈值单位为百分比 // coverageThreshold: { // global: { // statements: 90, // 每行 // functions: 80, // 每个函数 // branches: 90 // 分支覆盖率 // } // } }此时再次执行单元测试发现显示了测试覆盖率用浏览器 打开coverage目录下的index.html可以看到此时页面显示了测试报告3、配置快速执行命令package.json执行命令启动单元测试yarn test或yarn coverage4、项目配置一般通过脚手架生成的项目已经默认配置了测试框架比如React的项目配置了JestVue3.x项目默认配置了Vitest。Jest默认支持Commonjs如果你的项目不支持ESM需要安装babel/corebabel/preset-env进行转译。如果你的项目需要支持TS可以types/jest、babel/preset-typescript执行yarn test发现报错是因为需要配置babel配置babel安装插件在根目录下新建.babelrc文件// .babelrc { plugins: [ [ babel/plugin-syntax-jsx ] ], presets: [ babel/preset-env, babel/preset-react ] }注意如何支持或忽略.css文件如何忽略单行、函数或文件、目录5、快速上手单元测试比如我们有sum.jsexport function sum(a, b){ return a b; } export function mins(a, b){ return a - b; }为这个函数写测试文件import { sum, mins } from ./sum it(should add 1 2 to equal 3, () { expect(sum(1, 2)).toBe(3); }); test(mins 2 - 1 to equal 1, () { expect(min(2, 1)).toBe(1); })入门很简单只需要针对每个函数做一些预期的校验即可当不小心改动了源代码导致输出的结果和预期不符将会测试不通过这样就保证了代码功能的稳定。describe、test预留字段基本没有区别描述方式不同一个it should另一个test action三、项目中如何开始写单元测试写单元测试要考虑清楚几点测试的主要目的不是证明代码的正确而是为了发现错误。测试代码只考虑外部接口不考虑内部实现充分考虑数据的边界条件对重点、核心代码重点测试减少测试代码数量避免无用功基于需求写单元测试1、在项目根目录下新建tests目录将单元测试文件放在其中测试文件命名xx.test.js优点是可以更好的管理测试文件缺点是不好找到源文件2、在对应文件的目录下新建__test__目录测试文件放置其中优点就是容易找到执行文件但不容易过滤和管理1、给工具函数写单元测试给工具函数写测试函数是单元测试很重要的一个场景我们以金额千分位格式化处理函数为例通过单元测试发现问题。// 将数字千分位格式化后返回对应的字符串 export function getThousandFormatNum(num) { const str num ; const reg str.indexOf(.) -1 ? /(\d)(?(\d{3})\.)/g : /(\d{1,3})(?(\d{3})(?:$|\.))/g; return str.replace(reg, $1,); }单元测试import { getThousandFormatNum } from ./common describe(getThousandFormatNum, () { // 常规数字格式化 it(should return a string with thousand format, () { expect(getThousandFormatNum(1000)).toBe(1,000); expect(getThousandFormatNum(1000000)).toBe(1,000,000); expect(getThousandFormatNum(123456789)).toBe(123,456,789); }); // 格式化后和本身相同的数字 it(should return the same number if it is not greater than 999, () { expect(getThousandFormatNum(0)).toBe(0); expect(getThousandFormatNum(999)).toBe(999); }); // 格式化负数 it(should handle negative numbers correctly, () { expect(getThousandFormatNum(-1000)).toBe(-1,000); expect(getThousandFormatNum(-1000000)).toBe(-1,000,000); expect(getThousandFormatNum(-123456789)).toBe(-123,456,789); }); // 格式化带小数的数字 it(should handle decimal numbers correctly, () { expect(getThousandFormatNum(1234.56)).toBe(1,234.56); expect(getThousandFormatNum(1234567.89)).toBe(1,234,567.89); }); });执行单元测试2、给组件写单元测试快照测试前端主要就是组件但业务组件变动比较频繁所以倾向于给公共组件或组件库增加单元测试防止组件扩展或变更导致业务Bug。我们以APP.js组件为例写单元测试并生成快照。function App() { return ( div classNameApp HashRouter basename/ div style{{marginBottom: 20}} Link style{{marginRight: 20}} to/Home更新版本1/Link Link to/aboutAbout更新版本123/Link /div Suspense fallback{divLoading.../div} Routes Route path/ element{AFunction /}/Route Route path/about element{BFunction /} / /Routes /Suspense /HashRouter /div ); } export default App;给App.js写单元测试import { render, screen, act } from testing-library/react; import App from ./App; test(renders learn react link, async () { let tree; await act(async () { tree render(App /); }) const linkElement screen.getByText(/About更新版本123/i); expect(linkElement).toBeInTheDocument(); expect(tree).toMatchSnapshot(); });当我们改动App.js单元测试发现上个版本的快照更新了就会报错提醒检查如果更改没问题可以执行u更新快照3、模拟函数MockMock是单元测试中很重要的一部分他一般在下面场景中使用模拟数据模拟接口请求模拟定时器比如setTimout 1小时那每次测试花费一小时就疯了组件使用Redux怎么测试在组件中经常有一些引用的变量const mock jest.fn(); mock.mockReturnValue(42); mock(); // 42 mock.mockReturnValue(43); mock(); // 43模拟接口请求test(async test, async () { const asyncMock jest.fn().mockResolvedValue(43); // Promise await asyncMock(); // 43 });模拟函数有很多在实际使用过程中需要各种结合使用这里仅展示了最简单的使用。4、常用断言方法在工具函数测试过程中我们常常要判断变量类型和值测试框架往往提供了判断方法下面是Jest一些常见的判断更多可以查阅官网toBe判断测试结果为某个值not否定判断test(the best flavor is not coconut, () { expect(bestLaCroixFlavor()).toBe(coconut); }); test(the best flavor is not coconut, () { expect(bestLaCroixFlavor()).not.toBe(coconut); });toEqual检测引用类型递归检查属性和属性值toEqual会调用Object.is方法toBe const can1 { flavor: grapefruit, ounces: 12, }; const can2 { flavor: grapefruit, ounces: 12, }; describe(the La Croix cans on my desk, () { test(have all the same properties, () { expect(can1).toEqual(can2); // true }); test(are not the exact same can, () { expect(can1).not.toBe(can2); // true }); });toMatch匹配字符串规则正则匹配describe(an essay on the best flavor, () { test(mentions grapefruit, () { expect(essayOnTheBestFlavor()).toMatch(/grapefruit/); expect(essayOnTheBestFlavor()).toMatch(new RegExp(grapefruit)); }); });toBeTruthy匹配if条件为真drinkSomeLaCroix(); if (thirstInfo()) { drinkMoreLaCroix(); }四、查看单元测试的结果单元测试完成后执行测试命令yarn test或npx jest1、测试覆盖率解读Stmts (Statements)语句覆盖率即被测试覆盖的代码语句的百分比。在你的代码中92.85% 的语句被测试覆盖。Branch分支覆盖率即被测试覆盖的条件分支的百分比。在你的代码中100% 的分支被测试覆盖。Funcs (Functions)函数覆盖率即被测试覆盖的函数的百分比。在你的代码中83.33% 的函数被测试覆盖。Lines行覆盖率即被测试覆盖的代码行数的百分比。在你的代码中100% 的行被测试覆盖。Uncovered Line未覆盖的行号。这一列列出了未被测试覆盖的代码行的行号范围。2、测试信息解读Test Suites: 2 passed, 2 total这表示你有 2 个测试套件其中所有的 2 个测试套件都通过了。Tests: 8 passed, 8 total这表示你一共运行了 8 个测试其中所有的 8 个测试都通过了。Snapshots: 1 total这表示1个快照测试Snapshot Testing。Time: 2.703 s这表示测试运行的时间为 2.703 s 秒。3、参考Jest官网Jest实践指南最后下方这份完整的软件测试 视频教程已经整理上传完成需要的朋友们可以自行领取【保证100%免费】软件测试面试文档我们学习必然是为了找到高薪的工作下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料并且有字节大佬给出了权威的解答刷完这一套面试资料相信大家都能找到满意的工作。