为何要写单测
单元测试(Unit Test)作为持续集成实现中的一环,位于金字塔模型的底部,目标是证明代码的某个单元(被测试的主体)能按照预期工作,这样我们在开发过程早期就能发现问题。
一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。
说白话,就是确保你代码输出的是你想要的结果。
直接来看个例子。
项目使用了MomentJS处理时间相关的Case,前阵子Moment.js 宣布停止开发,进入维护状态,加上又有打包体积的优化需求,Day.js无疑是更好的选择,遂进行替换。
两者API基本相同,但某些Case下还是略有不同,比如:
// 返回某年第几周的首尾日期(MM/DD) function transforWeekInterval(year, weekNums = 1) { const now = moment(year); // 这里有一些边界的校验,略过... now.add(weekNums - 1, 'w'); return { start: now.startOf('isoWeek').format('MM/DD'), end: now.endOf('isoWeek').format('MM/DD') } } // output: transforWeekInterval('2020') -> {end: "01/05", start: "12/30"} |
针对这个函数有以下测试用例:
// Test Case 0 it('should transforWeekInterval return correct value in 2021 w10', () => { expect(transforWeekInterval('2021', 10)).toEqual({end: "03/07", start: "03/01"}); }); // Test Case 1 it('should transforWeekInterval return correct value in the first week 2020', () => { expect(transforWeekInterval('2020')).toEqual({end: "01/05", start: "12/30"}); }); |
当简单地将moment替换成day.js后,发现测试用例Case 1未通过,
expected: {end: "01/05", start: "12/30"} received: {end: "01/01", start: "01/01"} // 这里的修复 // dayjs().startOf('isoWeek') -> dayjs().isoWeekday(weekNums -1) |
如此,重构早期便可以发现错误,及时处理,否则在后面的测试环节需要投入更多的人力。也一定程度说明了单测的重要性,放心重构。
配置
Jest 是由 Facebook 维护的 JavaScript 测试框架,其重点是简单性。它适用于以下項目:Babel、TypeScript、Node.js、React、Angular 和Vue.js。
为什么使用Jest,不赘述,简单易用、代码覆盖率,也有比较清晰的报错信息。这里给到一份React项目常用的配置,对应的配置项在文档中可找到说明。
// In package.json "jest": { // 测试入口 "roots": [ "<rootDir>/src" ], // 覆盖率收集 "collectCoverageFrom": [ "src/**/*.{js,jsx}", ], // 初始化的一些配置,路径数组 // 在jest inital之前执行,比setupFilesAfterEnv更早,例如可以设置全局变量到global "setupFiles": [ // jsdom,不涉及dom可不引入 "react-app-polyfill/jsdom" ], // 类似,在jest inital之后执行,这一步能拿到jest的api进行扩展(故名AfterEnv) // 可以执行例如引入enzyme配置等 "setupFilesAfterEnv": [ "<rootDir>/src/setupTests.js" ], // 测试匹配文件 "testMatch": [ "<rootDir>/src/**/__tests__/**/*.{spec,test}.{js,jsx}", "<rootDir>/src/**/*.{spec,test}.{js,jsx}" ], "testEnvironment": "jest-environment-jsdom-fourteen", // 转译, babel插件引入 "transform": { "^.+\\.(js|jsx|ts|tsx)$": "<rootDir>/node_modules/babel-jest", "^.+\\.css$": "<rootDir>/config/jest/cssTransform.js", "^(?!.*\\.(js|jsx|ts|tsx|css|json)$)": "<rootDir>/config/jest/fileTransform.js" }, "transformIgnorePatterns": [ "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$", "^.+\\.module\\.(css|sass|scss)$" ], "modulePaths": [], "moduleNameMapper": { "^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy", // alias, 与Webpack一致 "@images(.*)$": "<rootDir>/src/images/$1", "@components(.*)$": "<rootDir>/src/components/$1", "@utils(.*)$": "<rootDir>/src/utils/$1", "@service(.*)$": "<rootDir>/src/service/$1" }, "modulePathIgnorePatterns": [ // test 中使用的mock文件,可忽略 "__mocks__" ], "moduleFileExtensions": [ "web.js", "js", "json", "web.jsx", "jsx", "node" ] } |
以上是JavaScript的配置,配置的目录结构测试文件位于src/**__tests__/,好处是贴近业务代码,也可以统一维护在根目录的test目录下。TS也差不多。可见配置上简单易懂,基本上可以说是开箱即用了。
Jest配置可以单独维护在具体的文件中,然后在cli中指定,也可以放在package.json。React的create-react-app则提供了开箱即用的jest配置和example。略微有坑点可能是Babel转译这块,Jest 无法直接解析 JSX 的语法,好在常见的错误都可以搜索得到。
代码覆盖率
覆盖率的意义在于分析未覆盖部分的代码,从而反推在前期测试设计是否充分,没有覆盖到的代码是否是测试设计的盲点。一般地,也作为测试考核的一个节点,但不应该成为重点。第二一个是增量覆盖率,可以使用istanbul.js等来统计。
在Jest cli中添加--coverage即可生成代码覆盖率报告,或者在配置文件中设置collectCoverage: true。
运行测试完毕后结果如下:
同时也会在根目录下生成coverage目录,里面是可视化的web文件,具体到覆盖行/分支,也有代码视图:
开发中要实现比较完好的单测覆盖,目测会增加20%-30%的开发量,所以需求评估时注意多给点时间...
个人理解测试既能保障产品交付,也可以提升代码质量,编写可测试代码应该是基本能力。
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理