should.js源码分析与学习

发表于 4年以前  | 总阅读数:831 次

背景

为了研究与学习某些测试框架的工作原理,同时也为了完成培训中实现一个简单的测试框架的原因,我对should.js的代码进行了学习与分析,现在与大家来进行交流下。

目录

  • ext
  • assertion.js
  • assertion-error.js
  • config.js
  • should.js
  • util.js

其中ext为文件夹,其余为js文件。

结构

其中should.js为整个项目入口,asssertion.js为should.js中的类,负责对测试信息进行记录。assertion-error.js为should.js定义了一个错误类,负责存储错误信息。config.js中存储了一些should.js中的一些配置信息。util.js中则定义了一些项目中常用的工具函数。

should.js

var should = function should(obj) {
    return (new should.Assertion(obj));
};

should.AssertionError = require('./assertion-error');
should.Assertion = require('./assertion');

should.format = util.format;
should.type = require('should-type');
should.util = util;
should.config = require('./config');

exports = module.exports = should;

should.js入口文件初始化了一个类,并将所有文件中其他的模块进行引入。同时将自己export出去,让自己能够被require到。

should.extend = function (propertyName, proto) {
    propertyName = propertyName || 'should';
    proto = proto || Object.prototype;

var prevDescriptor = Object.getOwnPropertyDescriptor(proto, propertyName);

Object.defineProperty(proto, propertyName, {
    set: function () {
    },
    get: function () {
        return should(util.isWrapperType(this) ? this.valueOf() : this);
    },
    configurable: true
});

return {
    name: propertyName, descriptor: prevDescriptor, proto: proto};
};

should.js自身定义了一个extend方法,用于兼容should.js的另一种调用方式,即should(obj)的方式等于should.js的常规调用方式obj.should,从而兼容另一种写法。 ​

should
    .use(require('./ext/assert'))
    .use(require('./ext/chain'))
    .use(require('./ext/bool'))
    .use(require('./ext/number'))
    .use(require('./ext/eql'))
    .use(require('./ext/type'))
    .use(require('./ext/string'))
    .use(require('./ext/property'))
    .use(require('./ext/error'))
    .use(require('./ext/match'))
    .use(require('./ext/contain'));

should.js中还定义了use方法,从而让我们能够自己编写一些类型判断例如isNumber等函数导入到项目中,从而方便进行测试。项目目录中的ext文件夹就是编写的一些简单的should.js的扩展。后面将在介绍扩展时对两者的工作原理以及使用方法进行介绍。

assertion.js

function Assertion(obj) {
    this.obj = obj;

    /**
     * any标志位
     * @type {boolean}
     */
    this.anyOne = false;

    /**
     * not标志位
     * @type {boolean}
     */
    this.negate = false;

    this.params = {actual: obj};
}

assertion.js中定义了一个Assertion类,其中any为should.js中的any方法的标志位,而not则为其not方法的标志位。

Assertion.add = function(name, func) {
    var prop = {enumerable: true, configurable: true};

    prop.value = function() {
        var context = new Assertion(this.obj, this, name);
        context.anyOne = this.anyOne;

        try {
            func.apply(context, arguments);
        } catch(e) {
            //check for fail
            if(e instanceof AssertionError) {
                //negative fail
                if(this.negate) {
                    this.obj = context.obj;
                    this.negate = false;
                    return this;
                }

                if(context !== e.assertion) {
                    context.params.previous = e;
                }

                //positive fail
                context.negate = false;
                context.fail();
            }
            // throw if it is another exception
            throw e;
        }

        //negative pass
        if(this.negate) {
            context.negate = true;//because .fail will set negate
            context.params.details = 'false negative fail';
            context.fail();
        }

        //positive pass
        if(!this.params.operator) this.params = context.params;//shortcut
        this.obj = context.obj;
        this.negate = false;
        return this;
    };

    Object.defineProperty(Assertion.prototype, name, prop);
};

assertion.js中的add方法在Assertion的原型链中添加自定义命名的方法,从而让我们能够打包一些判断的方法来进行调用,不需要重复进行代码的编写。该方法具体的使用方式我们在后面对扩展进行讲解时将会提到。 ​

Assertion.addChain = function(name, onCall) {
    onCall = onCall || function() {
        };
    Object.defineProperty(Assertion.prototype, name, {
        get: function() {
            onCall();
            return this;
        },
        enumerable: true
    });
};

addChain方法添加属性到原型链中,该属性在调用方法后返回调用者本身。该方法在should.js的链式调用中起着重要的作用。

同时,Assertion类还支持别名功能,alias方法使用Object对象的getOwnPropertyDescriptor方法来对属性是否存在进行判断,并调用defineProperty进行赋值。

Assertion类在原型链中定义了assert方法,用来对各级限制条件进行判断。assert方法与普通方法不同,它并未采用参数来进行一些参数的传递,而是通过assert方法所在的Assertion对象的params属性来进行参数的传递。因为在Assertion对象中存储了相关的信息,使用这个方法来进行参数传递方便在各级中assert函数的调用方便。具体使用方法我们将在扩展的分析时提到。

assert: function(expr) {
    if(expr) return this;

    var params = this.params;

    if('obj' in params && !('actual' in params)) {
        params.actual = params.obj;
    } else if(!('obj' in params) && !('actual' in params)) {
        params.actual = this.obj;
    }

    params.stackStartFunction = params.stackStartFunction || this.assert;
    params.negate = this.negate;

    params.assertion = this;

    throw new AssertionError(params);
}

Assertion类也定义了一个fail方法能够让用户直接调用从而抛出一个Assertion的Error。

fail: function() {
    return this.assert(false);
}

assertion-error.js

在此文件中,定义了assertion中抛出来的错误,同时在其中定义了一些信息存储的函数例如messagedetail等,能够让错误在被捕获的时候带上一些特定的信息从而方便进行判断与处理。由于实现较为简单,因此在此就不贴出代码,需要了解的人可以自己去查阅should.js的源码。

ext/bool.js

下面简单介绍一个Assertion的扩展的工作方式。让我们能够对should.js的工作原理有一个更加深刻的理解。

module.exports = function(should, Assertion) {
    /**
     * 判断是否为true
     */
    Assertion.add('true', function() {
        this.is.exactly(true);
    });
    /**
     * 别名为True
     */
    Assertion.alias('true', 'True');

    /**
     * 判断是否为false
     */
    Assertion.add('false', function() {
        this.is.exactly(false);
    });
    /**
     * 别名False
     */
    Assertion.alias('false', 'False');

    /**
     * 通过对象检查来判断对象是否为空
     */
    Assertion.add('ok', function() {
        this.params = {operator: 'to be truthy'};

        this.assert(this.obj);
    });
};

//should.js
should.use = function (f) {
    f(should, should.Assertion);
    return this;
};

//use
'1'.should.be.true();

通过上面的扩展模块代码以及should.js文件中的use函数,我们可以发现,use函数向扩展模块传入了should方法和Assertion构造函数。在bool.js这个扩展模块中,它通过调用Assertion对象上的add函数来添加新的判断方式,并且通过params参数来告诉Assertion对象如果判断失败应该如何提示用户。

感想

should.js如何实现链式调用?

Assertion类中,有一个addChain方法,该方法为某些属性定义了一些在getter函数中调用的操作方法,并且返回对象本身。通过这个方法,在ext/chain.js中,它为should.js中常见的语义词添加了属性,并通过返回对象本身来达到链式调用的Assertion对象传递。

['an', 'of', 'a', 'and', 'be', 'has', 'have', 'with', 'is', 'which', 'the', 'it'].forEach(function(name) {
    Assertion.addChain(name);
});

以下两段代码在结果上是一模一样的效果:
'1'.shoud.be.a.Number(); '1'.should.be.be.be.be.a.a.a.a.Number();

should.js的实现方式有哪些值得借鉴的地方?

  1. should.js中,通过将一些语义词添加为属性值并返回Assertion对象本身,因此有效解决了链式调用的问题。
  2. 通过Asseriton对象的属性来进行参数的传递,而不是通过函数参数,从而有效避免了函数调用时参数的传递问题以及多层调用时结构的复杂。
  3. should.js通过扩展的方式来添加其判断的函数,保证了良好的扩展性,避免了代码耦合在一起,通过也为其他人编写更多的扩展代码提供了接口。
  4. should.js通过extend方法,让should(obj)obj.should两种方式达到了相同的效果。通过在defineProperty中定义should属性并且在回调函数中用should(obj)的方式来获取obj对象。
  5. 通过抛出错误而不是返回布尔值的方式来通知用户,能够更加明显的通知用户,也方便向上抛出异常进行传递。

总结

总的来说,should.js是一个比较小而精的测试框架,他能够满足在开发过程中所需要的大部分测试场景,同时也支持自己编写扩展来强化它的功能。在设计上,这个框架使用了不少巧妙的方法,避免了一些复杂的链式调用与参数传递等问题,而且结构清晰,比较适合进行阅读与学习。

本文由哈喽比特于4年以前收录,如有侵权请联系我们。
文章来源:https://github.com/HJava/myBlog

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:6月以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:6月以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:6月以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:6月以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:6月以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:6月以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:6月以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:6月以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:6月以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:6月以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:6月以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:6月以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:6月以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:6月以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:6月以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:6月以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:6月以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:6月以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:6月以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:6月以前  |  398次阅读  |  详细内容 »
 目录