前端测试 基于 Istanbul 优雅地搭建前端 JS 覆盖率平台

酷家乐质量效能 for 酷家乐质量效能 · 2020年05月12日 · 最后由 Minko 回复于 2024年04月24日 · 16008 次阅读

前端测试在酷家乐质量保障体系中占据了一个很重要的位置。在设计工具中,前端不仅仅是接收后端数据进行渲染这么简单,而是包含了大量跟用户交互的行为,有很多重要的业务逻辑在里面。我们在前端进行了很多技术上的探索,希望能提升在前端测试领域中的效能。

本文会介绍一种前端 JS 代码测试覆盖率平台的搭建思路。提供了由简单到复杂的三个场景,并分别提供了不同场景下合适的实现代码覆盖率解决方案。

1. 场景 1

单版本,单次测试的覆盖率统计

要实现这种最简单的场景,有很多好用的插件可以选择:

插件 概述
ScriptCover 浏览器插件,使用方便,但 Chrome 商店已下架; 无需预插桩即可监控 browser 运行 js 代码,拿混淆后的代码毫无办法; 开源,但已停止维护;
Istanbul 覆盖率统计插件,可配多种各种 UT 框架和自动化框架使用; 编译时预插桩,代码在 UT 或 browser 运行时都可统计覆盖率; 开源,旧版已停止维护,新版 (请搜 nyc) 维护的很好; Js 写的,可扩展;
JsCoverage 单测覆盖率统计插件,可配多种各种 UT 框架和自动化框架使用; 编译时预插桩,代码在 UT 或 browser 运行时都可统计覆盖率; 开源,旧版已停止维护,新版 (请搜 JsCover) 维护的很好; Java 写的,可扩展;和 Istanbul 非常的像;
karma-coverage Karma 的插件,基于 Istanbul 写的; 如果用 Karma 做自动化,使用很方便; 较定制化,可模仿但不好直接扩展;

上述插件各有优劣,但均可以在一定程度上满足这个简单的场景。当然这个场景存在局限性:

  1. 单版本,单次测试
  2. 一般用于单元测试、前端自动化
  3. 报告生成在本地
  4. 不支持多次测试覆盖率叠加 如果需要突破上述局限性,可以参考一下场景 2 是否能满足需求。

2. 场景 2

单版本,多终端多次测试的覆盖率统计

要实现这种扩展场景,我们可以找到一些既有的实践,都是基于场景 1 中列举的插件的开源代码做的,举其中两个例子:

插件 概述
Istanbul-Middleware 基于 Istanbul 写的中间件; 需要前端代码预插桩并调用 middleware 提供的数据上传接口; 可统计一个前端工程在多个终端、多种方式测试的总覆盖率; 官方,开源,可扩展性强。
Istanbul 覆盖率统计插件,可配多种各种 UT 框架和自动化框架使用; 编译时预插桩,代码在 UT 或 browser 运行时都可统计覆盖率; 开源,旧版已停止维护,新版 (请搜 nyc) 维护的很好; Js 写的,可扩展;
JsCover++ 基于 JsCover 写的服务; 和 Istanbul-Middleware 很相似,但更定制化; 没开源。

当然,场景 2 依然存在一些局限性,于是我设计了场景 3,来满足当前场景的一些局限性:

  1. 单项目,单版本
  2. 数据无统一管理,只能导成文件
  3. Repo 无法自动匹配更新

3. 场景 3

多版本,多终端多次测试的覆盖率统计

3.1 摸透 Istanbul-Middleware 的使用方法

Git: https://github.com/gotwarlost/istanbul-middleware
根据 Git 中的说明以及对 demo 的研究,想要把 istanbul-middleware 应用到自己的前端工程,可以分为五步:

3.1.1. 部署 middleware

将 istanbul-middleware 的代码 clone 下来,并部署在某个服务器上,使默认接口可用。默认接口的设计详见 repo 的 readme。

3.1.2. 插桩

使用 Istanbul 的插件给前端代码插桩。
以酷家乐的某个前端 repo 为例,它使用 babel 编译打包,可使用 babel-plugin-Istanbul 插桩,具体实现方法,就是在 babel 的配置文件 .babelrc 中添加 istanbul 插件。参考:github.com/istanbuljs/babel-plugin-istanbul

对于其他类型的 JS 前端工程,可参考:github.com/istanbuljs/nyc
插桩前后代码对比如下。插桩后的代码可以在运行过程中,统计到某一行代码是否运行到,某一段逻辑是否被覆盖。

3.1.3. 浏览器运行插桩后代码

浏览器运行的代码如果成功的被 istanbul 插桩,可以通过 window.coverage 查看前端代码运行过程中的覆盖率数据,如图所示。当然这个数据是一堆 json 结构,不具可视化。

3.1.4. 前端代码调用 middleware 提供的接口

前端代码调用 middleware/coverage/client 接口,将 window.coverage 上传给中间件,供它统计。

3.1.5. 查看代码覆盖率报告

middleware 提供了看报告的页面,直接访问即可查看。

3.2. 如何改造

既然我们已经知道了 istanbul-middleware 的使用方法,那么我们应该如何基于它实现场景 3 呢?

3.2.1. 需求

多个 repo 的覆盖率分布统计;各个 repo 部署的各个环境分别统计;各个环境中,不同的版本分别统计。

3.2.2. 架构

3.3.3. 数据库

3.3.4. 接口设计


3.3.5. 结果展示

前端代码在浏览器运行时,通过调用 client 时配置不同参数,区分 repo、环境、版本。

查看特定 repo 特定 version 的覆盖率数据

查看某个路径下所有文件的覆盖率数据

查看覆盖率数据的原始数据源

关注我们

酷家乐质量效能团队热衷于技术的成长和分享,几乎每个月都会举办技术分享活动(海星日),每半年举办一次技术专题竞赛分享(火星日),并将优秀内容写成技术文章。

我们尽可能保障分享到社区的内容,是我们用心编写、精心挑选的优质文章。如果您想更全面地阅读我们的文章,请您关注我们的微信公众号"酷家乐技术质量"。

如果您有兴趣了解我们的职位和团队情况,请参考最新职位招聘,并联系 caibao@qunhemail.com。感谢您的阅读!

共收到 42 条回复 时间 点赞

这个不错

istanbul-middleware 的使用上 你们有遇到什么坑吗? 我们这边使用这个, 在报告生成上是非常不准确的,因为他那块依赖的库都是好几年前的了,所以后面我们发现通过 nyc 的方式就可以解决这个事情的

仅楼主可见
仅楼主可见
saii 回复

不知道你会在什么情况下发生统计不准确呢?

我们在实践过程中发现,有俩坑:

  1. 如果前端工程发布前做过代码混淆,那么覆盖率统计是不准确的,但未混淆代码的统计是基本准确的
  2. 产品迭代过程中会产生代码的 commit 和 merge,并产生不同版本;但不同版本的覆盖率的累加难以相应 merge(或者说我们未能找到支持覆盖率 merge 的库)

同时感谢你的回复,我们调研一下 nyc。

恒温 回复

感谢恒温大佬的支持👍


这个是我用 istanbul-middleware 生成报告的结果。
用的源码仓库是 https://github.com/vikpe/react-webpack-typescript-starter
然而 我再用 nyc 生成报告的话就是这样子

不知道你们有没有遇到过这种,所以我这边就直接弃用了 istanbul-middleware

saii 回复

说说我的理解:

  • nyc 是新版 istanbul,解决了大量 istanbul 存在的问题
  • nyc 和 istanbul 都用于【本地运行,本地统计覆盖率】的场景
  • istanbul-middleware 是 istanbul 的一个 用于【多端运行,在服务端统计覆盖率总和】的场景

然后说说问题:
istanbul 有问题,一般报告和实际覆盖的行,往往有一两行偏差。我们对比过 coverage 和报告中的覆盖行数确实是一样的,因此我们怀疑 babel-plugin-Istanbul 太旧,如果工程用到较新的 babel/webpack 编译时,插桩可能就不太准确

如果我理解没错的话:

  • 你贴的 Repo 似乎仅做了单元测试,属于【本地运行,本地统计覆盖率】的场景,这并不需要用到 middleware。升级 nyc 是没毛病的选择
  • 但 nyc 未提供 middleware,对于【多端运行,在服务端统计覆盖率总和】的场景,我暂时没有什么好的解决方案

不知道我理解对不对,你有什么好想法我们可以接着讨论

请教一下,在使用 istanbul-middleware 时,用 nyc instrument 插桩,发现这种情况:
可以插桩成功

var App = React.createClass({
    render: function () {
        return (
            1
        )
    }
});

插桩失败

var App = React.createClass({
    render: function () {
        return (
            <View style={{ flex: 1, justifyContent: "center", alignItems: "center", flexDirection: 'column' }}>
            <Text>你好吃了吗11111</Text>
        </View>
        )
    }
});

实在是找不到问题原因

你贴的 Repo 似乎仅做了单元测试,属于【本地运行,本地统计覆盖率】的场景,这并不需要用到 middleware。升级 nyc 是没毛病的选择
但 nyc 未提供 middleware,对于【多端运行,在服务端统计覆盖率总和】的场景,我暂时没有什么好的解决方案

其实我们这边目前的方案也是基本跟你上面的方案是一致的,只是说 istanbul-middleware 这块我们没有直接用它那个项目,因为这个项目归根到底就是一个 node 服务,然后调用了 istanbul 的 api 的。所以我们这边自己实现了一个 node 的服务,然后获取到 coverage 数据以后,通过 nyc 的 api 方式去生成了报告。不过目前这种方式还不能说完全没问题,因为我们现在刚把整个流程打通,还没有在业务项目里面真正的使用过。

请问一下,为什么我按照官方文档使用 nyc 绝对项目进行插桩,在服务运行过程中不能通过 window.coverage 查看覆盖率信息,但结束服务后能看到覆盖率报告呢,可能是哪些原因,求指点

1、使用 babel-plugin-Istanbul 插桩,和 babel-plugin-import 插件不兼容,导致编译失败
2、使用 nyc 插桩,插桩后 只能跟踪到编译后的源文件,且一个 7MB 的 js 文件插桩后变成 70 多 MB 了,对页面性能影响很大
这 2 个问题大家有遇到过吗

小晨晨 回复

第一个我最近也遇到过,网上的解决措施是修改 import 源码,不过误打误撞发现 babelrc 里面增加插件 @babel/plugin-transform-modules-commonjs 可以解决。

小晨晨 回复

第二个问题 没遇到 不过我觉的是不是你插桩的时候还插了第三库的代码才有这个问题

33楼 已删除
saii 回复


试了下,貌似没有效果😓

小晨晨 回复

你能方面生成一个最小的项目出来不?一起探讨下 因为我们这边尝试好几个项目就是这么解决的

saii 回复

这边针对 2 个 github 的项目进行了测试:
1、https://github.com/duxianwei520/react 不成功,运行页面仍然提示 undefined

2、https://github.com/zuiidea/antd-admin 加上@babel/plugin-transform-modules-commonjs 插件后能插桩成功

小晨晨 回复

项目 1 我这边是可以的。


原来是那个 commonJS 插件放的顺序的问题,@babel/plugin-transform-modules-commonjs 这个插件需要放在 import 插件前面才行,放在后面就会失败😑

请问下,你们有遇到过前端项目又依赖了自己开发的一些 sdk(js) 的情况吗? 这种你们是怎么进行插桩的吗?感觉 node_modules 中的代码都已经是压缩编译后的,针对这些插桩没办法还原回来映射的源码的情况。

saii 聊聊前端代码覆盖率 (长文慎入) 中提及了此贴 06月06日 15:13

大佬,babel 自动编译插桩能详细解释下吗?只需要配置.babelrc 这个文件就可以吗

24楼 已删除
仅楼主可见
itimetime 回复

在我们的工程实践中,是的。但具体用法参考这个啦:https://github.com/istanbuljs/babel-plugin-istanbul

saii 回复

没有实践过,但我认为可以这么做:每个 SDK 独立插桩,独立调用接口上报覆盖率信息,独立维护 sourcemap 进行映射。依赖 SDK 的总工程,是没有办法帮助已编译的包插桩的

匿名 #20 · 2020年07月15日

弱弱问一下,前端上报覆盖率信息的时候,repo 和版本信息是从哪里去获取的呢? 大佬可以提示下么

大佬们,请教一个事,使用 Istanbul-middleware,/coverage 在线观看报告时,渲染有问题,报告是黑白的。调用接口/download 下载到本地之后,观看报告,宣传则是正常的,你们有遇到过吗?这是什么原因啊?

匿名 #30 · 2021年01月26日

可以实现只查看特定分支与 master 的 diff 代码的覆盖率吗?

上报的数据你们是怎么存储的?

因为.vue 文件中也有 js,我想对.vue 文件插桩,今天看了下 nyc 的使用说明,有个--include 和--extension 开关,已经设置了

"nyc": {
  "extension": [
    ".js",
    ".vue"
  ],
  "include": [
    ".js",
    ".vue"
  ]
}

但插桩出来的结果为什么还只是.js 的,没有.vue 呢,我想问下楼主,nyc 支持.vue 吗,需要什么设置吗

你好,想请教一下,关于中间件 im 的接口 repo 是如何去设计的

你好,有个问题咨询下:前端代码和收集覆盖率的服务不在同一台机器,而且有两个 APP 同时测试,需要同时看这两个 APP 的覆盖率渲染页面。这种场景下怎么区分这两个 APPpost 的覆盖率数据那,楼主有相关的建议吗


请问有遇到过这样的问题吗?

kira5271 回复

这个你后来有方案了吗


您好,我这里碰见一个问题,能获取到报告的数据,但是没有样式

Kepler-ZZ 回复

这个问题你后来解决了吗 我这里也遇到了

现在打包后上传的代码地址都是绝对路径,怎么改成打包后部署的代码路径(既查看代码覆盖率是部署后的代码)

IMT 回复

这个你解决了吗?我也是这个问题,现在就像看到 部署后的源码,不想看本地的

mohaisnow 回复

解决了,路径问题

王稀饭 专栏文章:初探前端质量保障体系 中提及了此贴 10月06日 15:51
王稀饭 专栏文章:初探前端质量保障体系 中提及了此贴 10月06日 15:51
saii 回复

不是报告生成上是非常不准确的 而是插桩有问题

你好 ,我们公司是用 vite 打包的,请问下这个适用吗,如果不适用,有其他的替代品吗

感谢作者提供思路和干货,我封装了一系列的 istanbul 相关的库,若有需要的朋友可以看看:https://github.com/hemengke1997/istanbul-toolkit

Warflor 回复

可以使用的!

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册