• 更新于2020.07.20

    前端代码覆盖率又接入了新的项目,但是这次的前端用到了一个新的框架 easywebpack, 不过好在官方的文档是相当的详尽,所以在babel升级基本没有遇到什么大的问题。

    原以为一切的会比较顺利的进行, 但是测试的同事反馈说出现几个文件的覆盖率数据明显不正确,并且那几个文件的数据都是一样的。我们来看下结果数据
    image

    这里的四个覆盖率数据竟然是完全一样的,我们再详细看下某个文件的详细覆盖率数据

    image

    很明显 从11行的地方我们发现这里的代码与覆盖率的数据其实是完全对应不上的,我们在11行处根本就不存在有IF的代码逻辑。

    那现在的问题来了为什么会出现这种情况呢? 首先我们的覆盖率数据是有经过多次合并的,所以首先我们可能要怀疑是我们的覆盖率数据合并导致的问题。那我们就先验证下原生的网页上的覆盖率数据是否有问题吧

    image

    这里是从浏览器的控制台打印出来的结果,从上述的一些数据我们可以看出来,在11的时候,确实有一个if的语句,所以说明了一个问题 就是原本的文件覆盖率数据就已经错误了。

    那问题到底出在哪里呢?这里还是需要从打包的地方去分析这个问题了。

    以下是webpack的部分截图

    image

    这里分别了客户端渲染以及服务端渲染的打包,所以我们要重点看下loader.js里面做了啥事情。

    module.exports = function() {
    this.cacheable();
    return `
    import React from 'react';
    import ReactDom from 'react-dom';
    import { AppContainer } from 'react-hot-loader';
    import Entry from '${this.resourcePath.replace(/\\/g, '\\\\')}';
    const state = window.__INITIAL_STATE__;
    const render = (App)=>{
    ReactDom.hydrate(EASY_ENV_IS_DEV ? <AppContainer><App {...state} /></AppContainer> : <App {...state} />, document.getElementById('app'));
    };

    if (EASY_ENV_IS_DEV && module.hot) {
    module.hot.accept('${this.resourcePath.replace(/\\/g, '\\\\')}', () => { render(Entry) });
    }
    render(Entry);
    `;
    };

    针对要处理的js文件,看起来这里的loader又重新进行render了一次,而且如果细心一点 我们可能注意到一个很关键的因素,这里从return方法往下的第11行 刚好是 if (EASY_ENV_IS_DEV && module.hot) { 一个if语句, 所以其实我们所有错误的js的覆盖率都被这段的逻辑所影响到了。那我们原本的jsx的文件有没有被插桩呢?所以还是需要看下插桩后的文件到底长什么样。

    针对campCourse.jsx的打包文件

    image

    我们在6w多行的时候可以看到有这样的一个方法 针对的就是该文件的覆盖率数据

    然后我们再观察
    image

    在11w多行的时候, 也出现了同样的覆盖率数据,而且该数据与我们从浏览器读到的数据是一致的也就是错误的数据。到这里应该就能有一定的总结的,也就是说源码文件其实是有被插桩的,只是说它的数据最终被另外一个相同对象给覆盖了导致我们拿到了错误的数据。

    问题到了这里就要想着怎么解决了,其实istanbul提供另一些方式来过滤掉一些你不想插桩的代码。具体可以看 ignoring-code-for-coverage

    所以我们需要做的是忽略到这个文件的插桩,不过这个ignore添加在哪里还是有一定的技巧的 我们来看下最后的改动代码吧。

    module.exports = function() {
    this.cacheable();
    return `/* istanbul ignore file */
    import React from 'react';
    import ReactDom from 'react-dom';
    import { AppContainer } from 'react-hot-loader';
    import Entry from '${this.resourcePath.replace(/\\/g, '\\\\')}';
    const state = window.__INITIAL_STATE__;
    const render = (App)=>{
    ReactDom.hydrate(EASY_ENV_IS_DEV ? <AppContainer><App {...state} /></AppContainer> : <App {...state} />, document.getElementById('app'));
    };

    if (EASY_ENV_IS_DEV && module.hot) {
    module.hot.accept('${this.resourcePath.replace(/\\/g, '\\\\')}', () => { render(Entry) });
    }
    render(Entry);
    `;
    };

    我们需要将/* istanbul ignore file */的 内容添加在return 语句的后面,即第一行才行,如此下来的代码都不会被插桩处理了。

  • 哈哈,暂时没有考虑这个,不过很感谢😄

  • 这个我没有遇到过,如果你不嫌麻烦的话,可以弄一个最小的仓库内容出来 然后我帮你看看是什么情况,不然这样子描述 我帮不上什么忙

  • 看下16楼的回复,可以不回滚版本也是能解决那个问题的。

  • 嗯 验证了下 确实是这样子,感谢呀。

  • 我有个问题呀,你说的5w多的代码是指的你的源码吗?然后2w多是指的插桩后的报告吧。这个报告能够对应到你真正的源码不?
    我看到你的报告中的vplayer看上去不像是你们的源码呀

  • https://www.jianshu.com/p/fc038a92d1eb 看看这个文章 网上比较多 关于express设置跨域的方式

  • 这个是前端的跨域请求了 你可以改下你那个8088的服务,让他允许跨域请求就可以了。

  • 要怎么解决呢?有方法吗? 不过我看文档的说明是说15的版本是默认开启jsx的解析的,之前14的版本是不开启的,感觉貌似有点相反了。

  • 浏览器端实时插桩这种方案很难做的,现在基本上前端代码都是es6代码然后babel转es5 然后浏览器拿到的时候已经是被编译后的代码了,这个时候插桩怎么映射回源码的,这个事情很难

  • 能不能提供更详细的信息 我们这边typescript的插桩是可以的,并且有能够获取到覆盖率数据,但是我们这边都是前端项目,不是针对react-native的。

  • 你在build的过程中有没有报错(记得还要安装npm install @babel/plugin-transform-modules-commonjs),如果没有其实就是没问题的,然后你要验证这个插桩是否成功的话直接打开对应的页面,然后F12, 控制台输入window.coverage 看看是否有内容,当然更便捷的就是直接打开打包后的js文件,查看里面有没有cov,branchMap的字样。

  • 如果说你的项目是babel,并且版本是7的话 那确实只需要修改babelrc就可以了

  • instrument-backend-code node端的插桩的话 参考下这里

  • 我们这边node端只是做请求转发到后台的作用而已,基本没有实现什么业务逻辑,所以node端我们是没有插桩的。这块我了解的不多。可能需要你去到nyc官网去看下,理论上应该是可以的。

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

  • 项目1 我这边是可以的。


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

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

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

  • 告诉你个好消息,帮你试了下 你可以把nyc的帮忙降级到14.0.0 我用那个版本插桩就可以了,我不确定是不是15的版本是不是修改了什么,因为我们这边都是通过istanbul-plugins进行插桩的

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

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

  • 使用的babel编译的吗,用babel-plugins-istanbul


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

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

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