开发者社区> 开发者小助手_LS> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

卓越工程实践之—前端高质量单测

简介: 高单测覆盖率不能避免改动引发,小的改动引发就可能带来大的线上问题。
+关注继续查看

image.png

作者 | 范喆(六瓶)
来源 | 阿里开发者公众号

高单测等于高质量?

笔者负责的npm包是 ICBU信天翁低代码平台渲染引擎,160+应用 600+页面基于该引擎开发,内网日npm下载 1K+。经过不懈努力(CV),终于把单测提到了95%。

然而,虽然在覆盖率上获得了一些数据的改变,但作为开发者,想要的并不是数据上的完美,而是它真的完美(没BUG)。作为一个高频引用的底层库,改动一行代码都可以影响到用户意想不到的bug。

高单测覆盖率不能避免改动引发,小的改动引发就可能带来大的线上问题。

写好单测

issue=单测

每一个issue都有它命中注定的一个单测

在我们的项目中,用issue来管理用户需求。用户每发现一个问题都可以到我们指定仓库中去提issue,新增的issue触发机器人在钉钉群里艾特对应修改人,修复后机器人通知创建人。

image.png

在软件工程中,对单元测试的描述是“针对每一个单元的测试,以确保每个模块能正常工作为目标”。

在我们行覆盖率和分支覆盖率都很高的情况下,还需要有新的机制保证模块更稳定。除去那些框架还没探索到的业务场景,怎么样保证现在用户的一定没有问题?

于是有了issue即单测。

在现在的issue运作机制下,保证每一个单侧都有对应的issue。在仓库中新增了脚本tnpm run create-issue。

// package.json
"scripts": {
    "create-issue": "node ./script/issue_dev/createIssueTem.js",
}

// createIssueTem.js
/**
 * 快速创建issue示例
 */
const path = require('path');
const execSync = require('child_process').execSync;
const args = process.argv.slice(2);

const issueID = args[0];

if (!issueID) {
  console.error('需要输入issue id才能运行');
  process.exit();
}

const demoTarget = path.resolve(__dirname, `../../demo/issue_${issueID}`);
const demoSrc = path.resolve(__dirname, `../template/demo/base.md`);

const testTarget = path.resolve(__dirname, `../../test/issues-cov/${issueID}`);
const testSrc = path.resolve(__dirname, `../template/test/*`);
const specTarget = path.resolve(__dirname, `../../test/issues-cov/${issueID}/app.spec.tsx`);

execSync(`mkdir ${demoTarget}`);
execSync(`cp ${demoSrc} ${demoTarget}/`);
execSync(`sed -i '' 's/issueID/${issueID}/g' ${demoTarget}/base.md`);

execSync(`mkdir ${testTarget}`);
execSync(`cp ${testSrc} ${testTarget}`);
execSync(`sed -i '' 's/issueID/${issueID}/g' ${specTarget}`);

console.log(`创建${issueID}成功`);

在发布前把对应的demo做删除

// prebuild
// 构建前删除issue的demo
const fs = require('fs');
const path = require('path');
const ENV = process.env.BUILD_ENV == 'cloud';

function removeDir(dir) {
  let files = fs.readdirSync(dir);
  for (var i = 0; i < files.length; i++) {
    let newPath = path.join(dir, files[i]);
    let stat = fs.statSync(newPath);
    if (stat.isDirectory()) {
      //如果是文件夹就递归下去
      removeDir(newPath);
    } else {
      //删除文件
      fs.unlinkSync(newPath);
    }
  }
  fs.rmdirSync(dir); //如果文件夹是空的,就将自己删除掉
}

fs.readdir('./demo', (err, path) => {
  if (err) {
    console.log(err);
  }
  path.forEach((pathItem) => {
    if (pathItem.includes('issue') && ENV) {
      removeDir(`./demo/${pathItem}`);
      console.log(`删除${pathItem}`);
    }
  });
});

当我们运行tnpm run create-issue 123456,帮我们创建对应issue 123456的单测+demo,复用同一个template内容,可以在浏览器端看到demo,也可以在vs code中直接编写单测内容。

image.png

在demo中,可以直接点击gitlab链接跳转到对应issue。

image.png

这里拿一个简单的issue做演示:

image.png

对应的原子单测

describe('116193', () => {
  it('should work', async () => {
    const wrapper = mount(<App />);
    await sleep(10);
    wrapper.mount();
    expect(Object.keys(A)).toMatchSnapshot();
    expect(A.hasApplied).toBeDefined();
    return wrapper.unmount();
  });
});

单测非常简单,虽然只有两句expect,但这两句是只为这个issue存在,强行cp。

issue唯一单测覆盖,保证0改动引发。

在业界一些优秀的开源框架也是有同样的issue即单测的案例,比如mobx。

image.png

单测=文档

原子类单测可以极大程度保证代码稳定性,组件类可以描述开发者期望的用法。

单测即文档

image.png

闭环沉淀反哺

除此之外,issue的Milestone代表对应npm版本:

// changelog
# 1.24.0
1. 【FEAT】列表过滤提供类似表单的校验模式 #115827(cover by test)
2. 【FEAT】model内置属性应该不可枚举 #116193(cover by test)
3. 【FEAT】期望提供ref注解,方便平台侧做区分 #116364
4. 【Bug】watch 在正则的模式下,调用 silent validate 会导致 autoValidate 失效 #116242(cover by test)

issue的最好归宿就是cover by test。钉钉 -> issue -> npm changelog 相互对应,做到每个单测可溯源。

image.png

笔者负责的框架已经推行了一年,再回顾一下。值得思考的是,重头设计一次架构,是否能完美的解决现在的这些issue。

这些issue和单测都是走过的脚印,现在我们已经积累单测170+, 其中60+ issue原子类单测。不能保证0BUG。但可预见的是让用户放心用,不会有改动引发。单测是质量的守门神,帮助框架做好用户预期,一步步更稳健的前行。

最后

写单测最好的时间是项目开始前,其次是现在。


“阿里灵杰”问天引擎电商搜索算法大赛

点击这里,查看详情。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
libzip开发笔记(一):libzip库介绍、编译和工程模板
libzip开发笔记(一):libzip库介绍、编译和工程模板
105 0
好程序员web前端教程分享js模板模式
什么是模板模式? 模板模式是抽象父类定义了子类需要重写的相关方法。 而这些方法,仍然是通过父类方法调用的。 根据描述,“模板”的思想体现在:父类定义的接口方法。 除此之外,子类方法的调用,也是被父类控制的。
963 0
Java编程——编写高质量代码的思考
                                                 前言        最近在看《代码大全》,可以说是一本软件开发的百科全书,特别厚,但是干货也很多。
1014 0
编写高质量代码改善C#程序的157个建议[4-9]
原文:编写高质量代码改善C#程序的157个建议[4-9] 前言   本文首先亦同步到http://www.cnblogs.com/aehyok/p/3624579.html。本文主要来学习记录一下内容:   建议4、TryParse比Parse好   建议5、使用int?来确保值类型也可以为null   建议6、区别readonly和const的使用方法   建议7、将0值设为枚举的默认值   建议8、避免给枚举类型的元素提供显式的值   建议9、习惯重载运算符 建议4、TryParse比Parse好   如果注意观察,除string之外的所有的基元类型。
900 0
编写高质量代码改善C#程序的157个建议[4-9]
前言   本文首先亦同步到http://www.cnblogs.com/aehyok/p/3624579.html。本文主要来学习记录一下内容:   建议4、TryParse比Parse好   建议5、使用int?来确保值类型也可以为null   建议6、区别readonly和const的使用方法   建议7、将0值设为枚举的默认值   建议8、避免给枚举类型的元素提供显式的值   建议9、习惯重载运算符 建议4、TryParse比Parse好   如果注意观察,除string之外的所有的基元类型。
772 0
406
文章
1052
问答
来源圈子
更多
+ 订阅
文章排行榜
最热
最新