美文网首页学会js,玩转前后端
Nodejs的测试和测试驱动开发

Nodejs的测试和测试驱动开发

作者: uncle_charlie | 来源:发表于2016-06-21 09:54 被阅读221次

    测试是保证软件质量必不可少的一环。测试有很多形式:手动、自动、单元测试等等。这里我们只聊使用Mocha这个框架在Nodejs中实现单元测试。单元测试是测试等重要组成,这样的测试只对于一个方法,这样的一小段代码,实施有针对的测试。

    这里会逐步深入的讲解单元测试。首先是最简单的单元测试,没有外部依赖,只有简单的输入。接着是实用Sino框架实现stub等有依赖的测试。最后讲解如何单元测试异步代码。

    安装Mocha 和Chai

    安装Mocha:

    npm install mocha -g
    

    Mocha和其他的javascript单元测试框架,如:jasmine和QUnit不同,他没有assertion库。但是,Mocha允许你实用你自己的。最流行的Assertion库有should.js、expect.js和Chai,当然Nodejs内置的也可以使用。这里我们用Chai。

    首先创建一个package.json并安装Chai:

    touch package.json
    echo {} > package.json
    npm install chai --save-dev
    

    Chai包含三种assertion方式:should方式、expect方式和assert方式。个人喜欢expect式的,所以下面就使用这个方式了。

    第一个Test

    项目代码

    第一个例子,我们用测试驱动开发(TDD)的方式创建一个CartSummary的构造函数,这个函数会用来计算购物车的商品总数。测试驱动开发就是在实现功能之前先写单元测试,这样来驱动你设计可以与测试相适应的代码。

    测试驱动开发的步骤:

    1. 写一个测试,并且这个测试会失败。
    2. 写最少的代码来使整个测试可以通过。
    3. 重复。

    来看代码:

    // tests/part1/cart-summary-test.js
    var chai = require('chai');
    var expect = chai.expect; // we are using the "expect" style of Chai
    var CartSummary = require('./../../src/part1/cart-summary');
    
    describe('CartSummary', function() {
      it('getSubtotal() should return 0 if no items are passed in', function() {
        var cartSummary = new CartSummary([]);
        expect(cartSummary.getSubtotal()).to.equal(0);
      });
    });
    

    describe方法是用来创建一组测试的,并且可以给这一组测试一个描述。一个测试就用一个it方法。it方法的第一个参数是一个描述。第二个参数是一个包含一个或者多个assertion的方法。

    运行测试只需要在项目的根目录运行命令行:mocha tests --recursive --watchrecursive指明会找到根目录下的子目录的测试代码并运行。watch则表示Mocha会监视源代码和测试代码的更改,每次更改之后重新测试。

    我们测试不过,因为还没有完成功能代码。添加代码:

    // src/part1/cart-summary.js
    function CartSummary() {}
    
    CartSummary.prototype.getSubtotal = function() {
      return 0;
    };
    
    module.exports = CartSummary;
    

    测试就可以通过了:

    下一个测试:

    it('getSubtotal() should return the sum of the price * quantity for all items', function() {
      var cartSummary = new CartSummary([{
        id: 1,
        quantity: 4,
        price: 50
      }, {
        id: 2,
        quantity: 2,
        price: 30
      }, {
        id: 3,
        quantity: 1,
        price: 40
      }]);
    
      expect(cartSummary.getSubtotal()).to.equal(300);
    });
    

    这个测试时失败的。。。

    下面就来修改代码,让测试通过:

    // src/part1/cart-summary.js
    function CartSummary(items) {
      this._items = items;
    }
    
    CartSummary.prototype.getSubtotal = function() {
      if (this._items.length) {
        return this._items.reduce(function(subtotal, item) {
          return subtotal += (item.quantity * item.price);
        }, 0);
      }
    
      return 0;
    };
    

    Stub和Sinon

    假设我们现在需要给CartSummary添加getTax方法。最终的使用看起来是这样的:

    var cartSummary = new CartSummary([ /* ... */ ]);
    cartSummary.getTax('NY', function() {
      // executed when the tax API request has finished
    });
    

    getTax方法会使用量外的一个tax模块,包含一个calculate的方法。虽然我们还没有实现tax模块,但是我们还是可以完成getTax的测试。该怎么做呢?

    首先,安装Sinon:

    npm install --save-dev sinon
    
    

    安装Sinon之后,我们就可以给出tax.calculate的定义了:

    
    // src/part1/tax.js
    module.exports = {
      calculate: function(subtotal, state, done) {
        // implemented later or in parallel by our coworker
      }
    };
    

    创建完成tax.calculate之后就可以使用Sinon的魔法了。用Sinon给出一个tax.calculate的零时实现。这个零时的实现就是Stub(也叫做桩)。代码:

    // tests/part1/cart-summary-test.js
    // ...
    var sinon = require('sinon');
    var tax = require('./../../src/part1/tax');
    
    describe('getTax()', function() {
      beforeEach(function() {
        sinon.stub(tax, 'calculate', function(subtotal, state, done) {
          setTimeout(function() {
            done({
              amount: 30
            });
          }, 0);
        });
      });
    
      afterEach(function() {
        tax.calculate.restore();
      });
    
      it('get Tax() should execute the callback function with the tax amount', function(done) {
        var cartSummary = new CartSummary([{
          id: 1,
          quantity: 4,
          price: 50
        }, {
          id: 2,
          quantity: 2,
          price: 30
        }, {
          id: 3,
          quantity: 1,
          price: 40
        }]);
    
        cartSummary.getTax('NY', function(taxAmount) {
          expect(taxAmount).to.equal(30);
          done();
        });
      });
    });
    

    上面已经使用Sinon创建stub方法了。这里再细讲一下。使用sinon.stub方法创建Stub:

    var stub = sinon.stub(object,'method', func);
    

    object添加一个名称为method(第二个参数)的方法,方法体的实现在第三个参数中给出。

    上例中使用的方法体:

    function(subtotal, state, done) {
      setTimeout(function() {
        done({
          amount: 30
        });
      }, 0);
    }
    

    setTimeout方法是用来模拟真实环境的,在实际使用的时候肯定会有一个异步的网络请求来请求tax服务。方法体的替换在beforeEach里,这些代码会在测试开始之前执行。在所有测试完成之后调用afterEach,并把tax.calculate恢复到原来的模样。

    上面的例子也展示了如何测试异步代码。在it方法中指明一个参数(上例使用的是done)。Mocha会传入一个方法,并等待异步代码返回再结束测试。当然,这个等待是由超时时间的,一般是2000毫秒。如果异步代码的测试,没有按照上面的方法写的话,那么所有的测试都会通过。

    Sinon的"间谍"

    Sinon的间谍(spy)是用来完成另外一种替身测试的(test double),它可以用来记录方法调用。包括方法的调用次数、调用的时候的参数是什么样的以及是否抛出异常。下面就是更新后的测试:

    it('getTax() should execute the callback function with the tax amount', function(done) {
      var cartSummary = new CartSummary([
        {
          id: 1,
          quantity: 4,
          price: 50
        },
        {
          id: 2,
          quantity: 2,
          price: 30
        },
        {
          id: 3,
          quantity: 1,
          price: 40
        }
      ]);
    
      cartSummary.getTax('NY', function(taxAmount) {
        expect(taxAmount).to.equal(30);
        expect(tax.calculate.getCall(0).args[0]).to.equal(300);
        expect(tax.calculate.getCall(0).args[1]).to.equal('NY');
        done();
      });
    });
    

    在测试中添加了两个expect。getCall用来获取tax.calculate的第一次调用的第一个参数值,第二个getCall用来获取tax.calculate的第一次调用的第二个参数。主要可以用来检测被测试方法的参数是否正确。

    总结

    在本文中探讨了如何在Node中使用Mocha以及Chai和Sinon实现单元测试。希望各位喜欢。

    原文地址:https://www.codementor.io/nodejs/tutorial/unit-testing-nodejs-tdd-mocha-sinon

    相关文章

      网友评论

        本文标题:Nodejs的测试和测试驱动开发

        本文链接:https://www.haomeiwen.com/subject/sscgdttx.html