最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 别说你不会单元测试(二)名词解析

    正文概述 掘金(FESKY)   2021-03-26   565

    单元测试是前端知识体系中重要的一环,是中高级前端必须掌握的基础知识。然而在重视开发效率的日常工作中,单元测试往往没有被应用起来。

    这是我写的《别说你不会单元测试》系列文章的第二篇,在学习单元测试的过程中,你可能会认识很多新名词,以及海量测试相关的 NPM 库(比如 Mocha/Jest/Sinon/Instanbul/Jamine...)。这些名词是什么意思?这些库的职责又是什么?接下来的内容将一一介绍。

    往期文章链接在这里:

    • 《别说你不会单元测试(一)认识单元测试》

    断言

    从最基本的内容说起,无论是那种测试类型,都必须有正确性校验(如果分辨不出成功与否,那就没意义了鸭)。在代码中,我们使用断言来判断被测试代码的执行结果和预期是否一致。

    维基百科上是这样描述的:

    别说你不会单元测试(二)名词解析

    写一个最基础的例子来理解断言究竟是怎么一回事: 创建一个 assert.js 文件,复制以下代码,代码中我们写了一个 sum 方法,它将两个参数相加并返回。用 node 运行它 node assert.js 看看是会发生什么。

    const assert = require("assert");
    
    const sum = (a, b) => {
      return a + b;
    };
    
    assert(sum(2, 3) === 5, "2 plus 3 should equals to 5");
    

    不出意外的话(比如你没有装 node...),程序正常运行结束,什么都不会发生 ? 。因为这是符合我们预期的,2 + 3 的结果就是等于 5。弄个反例试一下呢,比如修改一下代码,把 5 改成 6 再执行一遍。

    assert(sum(2, 3) === 6, "2 plus 3 should equals to 6");
    

    ??? 恭喜解锁第一个报错,不必惊慌,开发阶段报错和线上奔溃比起来,根本不叫事儿~OK, 通过这个简单的例子,相信你肯定 get 到断言的作用了,就是在程序不符合预期的时候抛出异常,发出告警。

    assert.js:385
        throw err;
        ^
    
    AssertionError [ERR_ASSERTION]: 2 plus 3 should equals to 6
    

    上面的例子中,我们用到了 NodeJS 的 assert 模块。如果不想额外引入模块,自己写一个最基础的断言方法也很容易:

    function assert(condition, message) {
      if (!condition) {
        throw new Error(message);
      }
    }
    

    除了作为方法直接调用,assert 模块还提供了一些其他的方法,提供严格相等,正则匹配,异常断言等等的方法,可以直接看文档不一一列举了。

    assert.deepStrictEqual(actual, expected[, message])
    assert.match(string, regexp[, message])
    assert.throws(fn[, error][, message])
    ...
    

    社区还有很多其他的断言工具库,它们提供更灵活的断言风格,更丰富的工具方法,更优雅的异常展示等等。

    • 7.2k stars 的 chai
    • TJ 大神写的 should.js
    • ...

    测试替身(Test Double)

    回顾上一篇文章,我们提到单元测试会比端对端测试更加稳定,这就要求被测试代码不能有过多依赖(依赖越多,稳定性越差)。

    而实际情况是...被测试代码往往会和后端接口,定时器、动画、事件、第三方模块等等捆绑在一块,导致单元测试速度变慢,结果不稳定等等问题。

    例如下面的例子,单元测试阶段我们不需要和后端接口一块测试,需要测的是这个方法本身的逻辑。

    const getUserStars = async (userId) => {
      const { level } = await fetch(`/api/user/${userId}`, {
        method: "GET",
      });
      return "⭐⭐⭐⭐⭐".slice(0, level);
    };
    

    想要保证测试的速度和稳定性,就会用到测试替身。那怎么用呢,留个悬念。由于这部分的内容比较重要,可以展开讲一讲,临时决定将其作为下一篇文章的内容~

    社区比较受欢迎的测试替身库:

    • Sinonjs.
    • testdouble

    覆盖率

    写了测试代码,就会需要一个维度来衡量测试的程度怎么样,覆盖率就是用来描述测试覆盖程度的,通常有四种方式来统计覆盖率。

    • 语句覆盖(Statements)
    • 分支覆盖 (Branches)
    • 函数覆盖 (Functions)
    • 行覆盖 (Lines)

    通过下面的例子来看看有哪些区别,创建一个 coverage.js,复制以下代码。

    const add = (a, b) => {
      return a + b;
    };
    
    const multi = (a, b) => {
      return a * b;
    };
    
    let flag1 = true;
    let flag2 = false;
    
    if (flag1 || flag2) {
      add(2, 3);
    }
    

    使用 npmyarn 安装 nyc package,然后在 npm scripts 中加入 coverage,然后运行它 npm run coverage

    "scripts": {
      "coverage": "nyc --reporter=text-summary node coverage.js"
    }
    

    运行完会得到以下结果,可以看到语句覆盖、方法覆盖、行覆盖就是统计被测试代码中有多少条语句、多少个方法、多少行被执行了。比较严格的是分支覆盖,例子中的 flag1 || flag2 会产生四种组合,需要全部覆盖才可以达到分支覆盖率百分之百。

    =============================== Coverage summary ===============================
    Statements   : 87.5% ( 7/8 )
    Branches     : 50% ( 2/4 )
    Functions    : 50% ( 1/2 )
    Lines        : 85.71% ( 6/7 )
    ================================================================================
    

    通常在没有特殊说明的情况下,描述单元测试覆盖率使用的是语句覆盖。

    例子中用到了 nyc 这个包, 它是另一个非常强大的开源库 Instanbul 的 cli 版本。使用 nyc 的配置项 --reporter=lcov --report-dir=./coverage 还可以生成一份测试覆盖率报告的 HTML 文件。

    执行环境

    如果你从来没写过单元测试,在动手写前端代码的单元测试时可能会感到困惑。就是为什么明明写的是在浏览器跑的代码,却可以用 node 运行起来。

    这里指的浏览器跑的代码,包括但不限于以下 WebAPI:

    • document、window 对象
    • addEventListener
    • appendChild/removeChild
    • XMLHttpRequest
    • Storage

    大方向上看无非就两种方案:

    • 用 Node 启动浏览器或无头浏览器来运行前端代码
    • 在 Node 中模拟浏览器环境,把所有的 WebAPI 全部模拟实现一遍

    第一种方案听起来更加靠谱,然而实际上真实运行起来的速度堪忧,浏览器消耗的内存远大于被测试代码本身。通常在单元测试阶段,我们使用的是第二种方案,在 Node 端使用轻量级的浏览器环境模拟实现。

    浏览器的环境模拟库比较受欢迎的是 JSDOM

    const assert = require("assert");
    const { JSDOM } = require("jsdom");
    
    const { window } = new JSDOM(
      `<!DOCTYPE html><body><button id="btn">Buy It</button></body>`
    );
    const { document } = window;
    const button = document.querySelector("#btn");
    
    // Event
    button.addEventListener("click", spy);
    button.click();
    
    assert(spy.callCount === 1);
    assert(spy.args[0][0].target === button);
    
    // DOM API
    const block = document.createElement("div");
    block.innerHTML = "Block";
    document.body.appendChild(block);
    
    assert.deepStrictEqual(
      document.body.innerHTML,
      '<button id="btn">Buy It</button><div>Block</div>'
    );
    

    基于 JSDOM,就可以再 Node 端去模拟一些 DOM 的操作,事件触发等等,它是我们在 Node 端跑前端单元测试的环境基础。可以把上面的代码拷贝一下,创建一个 jsdom.js 文件,使用 npm 后 yarn 安装 jsdom,然后跑起来感受一下。

    测试框架

    到目前为止,我们的“测试代码”都还是散乱无章的,没有任何约束,也没有办法看到总体的测试结果。这时候测试框架就出现了,它做的事情主要是下面这些:

    提供全局的方法、运行骨架,社区的测试框架很多,不过基本用法都差不多。以 mocha 为例,你可以在一个测试文件中,直接使用 describe 关键字来定义一个分组,it (或 test) 来定一个具体的单元测试。在分组内还可以使用 beforeafterbeforeEachafterEach 等关键字来注册一个钩子方法,它们会分别在组开始、组结束、单个测试开始、单个测试结束时运行。

    // 分组
    describe("suitcase", () => {
      // hooks
      before(() => {
        console.log("before");
      });
      after(() => {
        console.log("after");
      });
      beforeEach(() => {
        console.log("beforeEach");
      });
      afterEach(() => {
        console.log("afterEach");
      });
    
      // 单个单元测试
      it("add()", () => {
        assert(add(2, 3) === 5);
      });
    
      it("addAsync()", async () => {
        assert((await addAsync(2, 3)) === 5);
      });
    });
    

    it 方法包裹了一个具体的测试用例,当这个用例断言失败时,测试框架就会认为这个用例执行失败了。如果需要执行异步代码,加上 async 关键字即可。

    组织代码:通常测试框架允许你自定义单元测试代码如何组织,是以 .spec.js 结尾,还是 .test.js 结尾;测试文件放在哪个目录下等等。
    执行测试:执行测试是测试框架最基本的任务,它会根据配置约定,从项目中把所有符合条件的测试文件找出来,执行这些文件内的测试代码。
    输出执行结果:执行完之后当然就是给出执行结果了,告知开发者哪些测试通过了,哪些不通过。

    社区常见的测试框架库有以下这些:

    • Jest
    • Mocha
    • Ava
    • ...

    结语

    这篇文章介绍了单元测试过程中的会出现的各类名词的含义、解答一部分困惑、以及对应分类下社区常见的库都是哪些,它们的基本用法等等。希望看完对你理解单元测试有所帮助。

    下期预告:从 Sinon.js 入门测试替身,欢迎点赞+关注


    起源地下载网 » 别说你不会单元测试(二)名词解析

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元