登录
注册
node.js 学习社区
用mocha+should+supertest为Web应用的Node服务器 编写BDD测试

阿拉磕巴

2014-12-30 23:11

用mocha+shouldjs+supertest编写测试简单、有趣、并且易于上手。首先让我们来看看这三者各自在一个测试过程中发挥的作用。

mocha

mocha是TJ编写的测试框架。如果用过jasmine的话,你对mocha的风格应该也不会陌生。下面是mocha文档上用mocha和Node原生测试模块assert编写的,对ES数组的indexOf方法进行测试的例子:

var assert = require("assert");
describe('Array', function(){//描述测试对象Array
  describe('#indexOf()', function(){//描述测试的方法indexOf()
    it('should return -1 when the value is not present', function(){//描述行为
      //实际的测试代码
      assert.equal(-1, [1,2,3].indexOf(5));
      assert.equal(-1, [1,2,3].indexOf(0));
    })
  })
}) 

实际上mocha作为测试框架,并没有提供实”assert”这样的判断方法。它只是在给it方法传入的函数中捕捉被抛出的异常,并予以处理。所以我们甚至可以这样:

it('should return -1 when the value is not present', function(){     
  if([1,2,3].indexOf(5)!=-1){
    throw new Error('expect -1, but '+[1,2,3].indexOf(5)+' returned.');
  }
}) 

mocha更多的是为整个测试过程提供开始、控制的手段,把所有的测试用例有序的、合理的放在一起,进行测试。比如说,如果我们的每个测试用例在实际执行之前,都需要执行一段相同的、用于初始化的代码,我们就可以用beforeEach方法把这些代码集合起来:

describe('hooks', function() {
  beforeEach(function(){
    // runs before each test in this block
  })
  it('should...',function(){
    // your assert code
  })
  ..
}) 

这样在每个it执行前,都会先执行用beforeEach中的代码。除了beforeEach,我们还可以使用before、after、afterEach来帮助我们代码的编写。

mocha的运行方式是直接执行”mocha“命令(当然别忘了先装Node,并以global方式安装mocha),然后mocha就会寻找当前目录名为test的文件夹下的所有js文件作为mocha的测试文件(./test/*.js)进行测试。如果我们在编写测试的过程中出现了问题,想单独运行某一个describe或it下的测试,我们可以用only方法让mocha忽略其他测试:

describe('Array', function(){
  describe.only('#indexOf()', function(){//只有这个描述下的测试会进行
    ...
  })
}) 

反过来,我们也可以通过把describe和it替换成xdescribe和xit来忽略掉一个特定的测试:(一时穿越了)我们也可以通过skip方法来忽略掉一个特定的测试:

 describe('Array', function(){
  describe('#indexOf()', function(){
    it.skip('should return -1 unless present', function(){
    })
    it('should return the index when present', function(){
    })
  })
})  

当然,最不能忽略的是对异步操作进行测试,通过在传入的匿名函数上声明done方法,并在异步返回后执行done方法,我们可以对异步操作进行测试:

 describe('a suite of tests', function(){
  this.timeout(500);

  it('should take less than 500ms', function(done){
    setTimeout(done, 300);
  })

  it('should take less than 500ms as well', function(done){
    setTimeout(done, 200);
  })
})  

除了上面的这些内容,mocha还有很多丰富选择可以让我们配置(我们甚至可以给mocha写一个配置文件),这里就不一一细说。

should

BDD风格的测试写起来非常的有乐趣,比如用shouldjs来写的话,编程就像是说话一样的自然:

 var user = {
    name: 'tj'
  , pets: ['tobi', 'loki', 'jane', 'bandit']
};

user.should.have.property('name', 'tj');
//user对象应该有一个名为name的属性,并且name的值为'tj'

user.should.have.property('pets').with.lengthOf(4);
//user对象应该有一个名为pets属性,并且pets的长度为4  

对于should,基本上只要我们有需要的时候去查阅文档即可,这里不多阐述,举文档上和mocha一起使用的例子:

var should = require('should');
var mylib = require('mylib');


describe('mylib', function() {
  it('should have a version with the format #.#.#', function() {
    lib.version.should.match(/^\d+\.\d+\.\d+$/);
  });
}); supertest  

既然是对服务器端编写测试,我们就少不了用来模拟浏览器(等客户端)行为的工具。supertest基于superagent,提供了更高级的接口。从superagent的名字我们就可以初步了解supertest的作用。(其实我是在为自己在后面分不清superagent和supertest找借口)

使用supertest发送请求的方式如下:

var request = require('supertest');
agent = request('http://localhost:5555');//设定协议、域名、端口。

agent.get('/').expect(200, function(err){//向http://localhost:5555/发送GET请求,预期
  console.log(err);                      //状态码为200,回调函数对异常进行处理。不设置
});                                      //回调函数会直接抛出异常。

agent.get('/say_hello').expect(function(res){//向http://localhost:5555/say_hello
                                             //发送GET请求。自定义返回结果的处理。
   if(res.text!='hello world!'){
      throw new Error('expect "hello world!" but other returned');
   }
}); 

如果返回的请求中的报头有SET-COOKIE字段,supertest就会像浏览器一样将其记录下来,并在以后发送的请求中带上这一cookie字段。这样supertest就能可以持久化储存cookie信息了,并对需要验证(登录)的服务器请求进行测试。关于这一点,具体可见supertest文档中的例子(“Here’s an example with mocha that shows how to persist a request and its cookies”处)。

这里着重讲一点,如果我们进行测试的目标服务器使用的是php,并且php接收的是POST方法的话,我们需要额外调用type(‘form’)方法,来模拟表单发送请求。这个问题同样在Angular的http service也会遇见,这里就不多阐述了。

最后举个我实际编写使用的测试例子(有改动):

describe('/login.php',function(){
 it('should return login success info',function(done){ 
   agent
     .post('/login.php')
     .type('form')
     .send({
       name:'oyyd',
       password:'password'
     })
     .expect(200)
     .expect(function(res){ 
        var jsonRes = JSON.parse(res.text); 
        jsonRes.should.have.property('success',true);
     })
     .end(done);
 });
}); 


回复 · 0

发表回复

你可以在回复中 @ 其他人