资讯专栏INFORMATION COLUMN

should.js源码分析与学习

Turbo / 2431人阅读

摘要:结构其中为整个项目入口,为中的类,负责对测试信息进行记录。通过抛出错误而不是返回布尔值的方式来通知用户,能够更加明显的通知用户,也方便向上抛出异常进行传递。

背景

为了研究与学习某些测试框架的工作原理,同时也为了完成培训中实现一个简单的测试框架的原因,我对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) {
    Assertion.add("true", function() {
        this.is.exactly(true);
    });
    
    Assertion.alias("true", "True");

    Assertion.add("false", function() {
        this.is.exactly(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的实现方式有哪些值得借鉴的地方?

should.js中,通过将一些语义词添加为属性值并返回Assertion对象本身,因此有效解决了链式调用的问题。

通过Asseriton对象的属性来进行参数的传递,而不是通过函数参数,从而有效避免了函数调用时参数的传递问题以及多层调用时结构的复杂。

should.js通过扩展的方式来添加其判断的函数,保证了良好的扩展性,避免了代码耦合在一起,通过也为其他人编写更多的扩展代码提供了接口。

should.js通过extend方法,让should(obj)obj.should两种方式达到了相同的效果。通过在defineProperty中定义should属性并且在回调函数中用should(obj)的方式来获取obj对象。

通过抛出错误而不是返回布尔值的方式来通知用户,能够更加明显的通知用户,也方便向上抛出异常进行传递。

总结

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/87723.html

相关文章

  • 前端单元测试初探

    摘要:本文只讨论单测的范畴,对集成测试有兴趣的话,可以看下的集成测试代码。前端单测现状测试本质上就是假定一个输入,然后判断得到预期的输出。 原文发于我的博客:https://github.com/hwen/blogS... 要不要写单测? 关于这个 cnode 上就有个很有意思的讨论 做个调查,你的 Node 应用有写单测吗? 看完这个应该会有结论?如果没有,就回帖跟别人探讨下~ 测试 测试...

    isLishude 评论0 收藏0
  • 单元测试学习总结

    摘要:通过添加一个回调函数通常命名为给方法,就会知道,它应该等这个函数被调用的时候才能完成测试。此外提供了一些钩子函数和。这些钩子函数可以用于设置测试的先决条件或者对测试进行清理。钩子函数会按照它们被定义的顺序运行。 Mocha 的安装和使用 1. 安装 使用npm全局安装: npm install -g mocha 安装Mocha >= v3.0.0,npm的版本应该>=v1.4...

    netScorpion 评论0 收藏0
  • 聊一聊前端自动化测试

    摘要:在真正写了一段时间的基础组件和基础工具后,才发现自动化测试有很多好处。有了自动化测试,开发者会更加信任自己的代码。由于维护测试用例也是一大笔开销毕竟没有多少测试会专门帮前端写业务测试用例,而前端使用的流程自动化工具更是没有测试参与了。 本文转载自 天猫前端博客,更多精彩文章请进入天猫前端博客查看 前言 为何要测试 以前不喜欢写测试,主要是觉得编写和维护测试用例非常的浪费时间。在真正写了...

    wthee 评论0 收藏0
  • Mocha中文文档

    摘要:中文文档这个是对文档的翻译,都是我一个字一个字敲出来的。任何钩子函数在执行的时候都可以传递一个可选的描述信息,可以更容易地准确指出测试中的错误。不给测试用例传递一个回调函数,就是被等待实现的测试用例,但同样会在报告中体现出来。 mocha中文文档 这个是对mocha文档的翻译,都是我一个字一个字敲出来的。水平有限,激情无限,欢迎大家批评指正。文档我也放在了我的github上,后续,我会...

    lentrue 评论0 收藏0
  • MEAN.js 文档

    摘要:感谢使用框架本文档涵盖构建应用所需的基础知识。用于数据校验的组件及相关文件在此目录进行管理。除了自定义中间件外,还是用了诸多第三方的中间件,它们是五测试我们使用组件对服务端代码进行测试。识别当前导航从已有导航中删除给定标识的导航配置。 本文同步至个人博客 MEAN.js 文档,转载请注明出处。 Overview 感谢使用 MEAN.js 框架! 本文档涵盖构建 MEAN 应用所需的基础...

    Hydrogen 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<