最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 剖析断言库 should.js 实现原理

    正文概述 掘金(descire)   2021-05-25   442

    一、前言

      断言库是单元测试的重要组成部分,在编写单元测试代码时,通过断言库来描述代码逻辑的预期效果,从而验证代码逻辑的正确性。

      断言库一般分为两大类:TDD(Test Driven Development)和 BDD(Behavior Driven Development)

    • TDD: assert.js
    • BDD: should.js 和 expect.js

      BDD 与 TDD 的区别主要在于:TDD 解决了代码级别的验证,但是测试代码与需求的符合问题解决的不是很好,而 BDD 则是让除了开发人员之外更多的角色参与到用例的评审,确保需求与测试代码相匹配。

      下面是 TDD 风格断言库 assert.js 的示例代码:

      assert.typeOf(foo, 'string');
      assert.equal(foo, 'bar');
      assert.lengthOf(foo, 3)
      assert.property(tea, 'flavors');
      assert.lengthOf(tea.flavors, 3);

      再看 BDD 风格断言库 should.js 的示例代码:

      foo.should.be.a('string');
      foo.should.equal('bar');
      foo.should.have.lengthOf(3);
      tea.should.have.property('flavors').with.lengthOf(3);

      相比较下,可读性更强,更容易让非开发人员理解断言的意思。

      接下来,本文就带大家一起探索 should.js 的实现原理。

    二、扩展对象属性

    const num = 10;
    num.should.be.above(8);

      当我们使用 should.js 时,他会为所有对象挂载一个名为 should 的属性。这个主要是依靠 JavaScript 中属性的查找机制,利用 Object.defineProperty 方法在对象的原型对象上进行扩展:

    class Assertion {
      // ...
    };

    const should = (obj) => {
      return new Assertion(obj);
    };

    Object.defineProperty(Object.prototype, 'should', {
      set() {},
      get() {
        return should(this);
      },
      configurable: true
    })

      Object.defineProperty 方法需要手动做健壮性处理,当属性没有被成功定义时,会抛出一个 TypeError。相比较下,使用 ES6 提供的元编程静态方法 Reflect.defineProperty 可以解决这一问题:

    const ans1 = Reflect.defineProperty(Object.prototype, 'should', {
      set() {},
      get() {
        return should(this);
      },
      configurable: false,
    })

    const ans2 = Reflect.defineProperty(Object.prototype, 'should', {
      set() {},
      get() {
        return should(this);
      },
    })

    console.log(ans1, ans2); // true false

      上述示例中第一次设置 should 时将 configurable 属性设置为 false,再次设置 should 属性的描述符时,不会抛出 TypeError,而是通过 false 返回值告诉开发者此次操作失败。

    三、添加链式调用

      链式调用的实现方式主要是在方法中将当前实例返回:

    const should = (obj) => {
      return new Assertion(obj);
    };

      这样就可以继续调用当前实例上的属性或者方法。

    四、断言方法机制

    class Assertion {
      constructor(obj) {
        this.obj = obj;
      }

      above (n) {
        return this.assert(this.obj > n);
      }

      assert (expr) {
        if (expr) {
          return console.log('success');
        }
        return console.log('fail');
      }
    };

      前文通过 Reflect.defineProperty 方法扩展 should 属性时,返回 Assertion 断言类的实例,该实例上挂载诸如 above 这样的断言方法,但是他们内部都是通过 assert 方法来判断表达式返回值的真假来实现断言功能。

    五、优化断言失败提示

    const { AssertionError } = require('assert');

    class Assertion {
      constructor(obj) {
        this.obj = obj;
      }

      above (n, message) {
        this.params = { operator: 'should be above ' + n, message };
        return this.assert(this.obj > n);
      }

      assert (expr) {
        if (expr) {
          return console.log('success');
        }
        let { message } = this.params;
        if (!message) {
          message = `expected ${this.obj} ${this.params.operator}`
        }

        const err = new AssertionError({
          message,
          actual: this.obj,
          stackStartFn: this.assert,
        })

        throw err;
      }
    };

      这里利用 Node.js 提供的 AssertionError 类来显示断言失败的函数调用堆栈和友好信息。在开发者未手动传入 message 的情况下,可以根据对外提供的方法构造一个兜底文案,提高开发体验。

    六、结构助词

    num.should.be.above(8);

      类似 be 这样的结构助词,不影响断言逻辑的判断,只是用来增强断言的可读性。

      同样可以采用 Reflect.defineProperty 方法来实现,但是需要注意 should 属性返回的是 Assertion 的实例,所以这些属性应该扩展在 Assertion 的原型属性上。

    ['an', 'of', 'a', 'and', 'be', 'have', 'with', 'is', 'which', 'the'].forEach(name => {
      Reflect.defineProperty(Assertion.prototype, name, {
        get () {
          return this;
        }
      })
    })

    七、否定属性

    class Assertion {
      constructor(obj) {
        this.obj = obj;
      }

      assert (expr) {
        let actualExpr = this.negate ? !expr : expr;

        // 非关键代码省略
      }

      get not () {
        this.negate = !this.negate
        return this;
      }
    }

      示例上添加 negate 属性来标记当前的状态。这里特别需要注意 not 属性中为什么不使用 this.negate = false,主要是考虑类似双重否定的情况:

    const num = 20;
    num.should.not.not.be.above(10);

    八、插件系统

      随着应用场景越来越复杂,我们会在 Assertion 类上添加越来越多的方法,这种方式存在以下两个缺陷:

    • 对 Assertion 类侵入性很强,增加了额外的维护成本。
    • 包大小问题
    // 插件注册方法
    should.use = function(plugin) {
      plugin(Assertion);
      return this;
    }

    Assertion.add = function(name, fn) {
      Object.defineProperty(Assertion.prototype, name, {
        value () {
          fn.apply(this, arguments);
        }
      })
    }

    function numberPlugin(Assertion) {
      Assertion.add('above', function (n, message) {
        this.params = { operator: 'to be above ' + n, message };
        return this.assert(this.obj > n);
      });

      Assertion.add('below', function(n, message) {
        this.params = { operator: 'to be below ' + n, message };
        return this.assert(this.obj < n);
      });
    }
    // 注册插件
    should.use(numberPlugin);

      通过插件机制,将断言方法与 Assertion 类解耦合。并且开发者可以根据自己的使用场景定制断言方法,从而解决包大小的问题。

    九、写在最后

      以上就是本文的全部内容,希望能够给你带来帮助,欢迎「关注」「点赞」「转发」


    起源地下载网 » 剖析断言库 should.js 实现原理

    常见问题FAQ

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

    发表评论

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

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

    联系作者

    请选择支付方式

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