——————————————————————————————————————

单元测试Express/NodeJs

个人理解,

1,如果不是测试http请求的单元测试,用Mocha, Chai等基本够用了,因为可以直接调用测试函数或者方法,传进输入参数值,函数执行完返回输入,对输出断言即可,即可以对此函数独立进行测试。并且可以用istanbul和mocha-lcov-reporter出测试覆盖率报告,也可以出html的形式的报告

另外,如果此函数中需要调用网络上或者其他依赖的api,可能需要mock此api的输入输出,此时可用到Nock包做mock相关的事情。

Sample:

https://github.com/schulzetenberg/nock-test/blob/master/index.js

https://github.com/schulzetenberg/nock-test/blob/master/test/index.spec.js

//被测试的函数

/*jshint esversion: 6 */

var Q = require('q');
var Client = require('node-rest-client').Client;

exports.getUserFollowers = function(username) {
  var defer = Q.defer();

  var client = new Client();

  //此处需要被mock
  var request = client.get(`https://api.github.com/users/${username}/followers`, function(data, response) {
    defer.resolve(data);
  });

  request.on('requestTimeout', function(req) {
    req.abort();
    defer.reject("Request has expired");
  });

  request.on('responseTimeout', function(res) {
    defer.reject("Response has expired");
  });

  request.on('error', function(err) {
    defer.reject("Request error", err.request.options);
  });

  return defer.promise;
};

//单元测试,用nock做mock

var expect = require('chai').expect;
var nock = require('nock');
var getUserFollowers = require('../index').getUserFollowers;

describe('GET followers', function() {
  beforeEach(function() {
    var followersResponse = [{
      "login": "octocat",
      "id": 583231,
      "avatar_url": "https://avatars.githubusercontent.com/u/583231?v=3",
      "gravatar_id": "",
      "url": "https://api.github.com/users/octocat",
      "html_url": "https://github.com/octocat",
      "followers_url": "https://api.github.com/users/octocat/followers",
      "following_url": "https://api.github.com/users/octocat/following{/other_user}",
      "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
      "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
      "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
      "organizations_url": "https://api.github.com/users/octocat/orgs",
      "repos_url": "https://api.github.com/users/octocat/repos",
      "events_url": "https://api.github.com/users/octocat/events{/privacy}",
      "received_events_url": "https://api.github.com/users/octocat/received_events",
      "type": "User",
      "site_admin": false
    }, {
      "login": "nanocat",
      "id": 583233,
      "avatar_url": "https://avatars.githubusercontent.com/u/583233?v=3",
      "gravatar_id": "",
      "url": "https://api.github.com/users/nanocat",
      "html_url": "https://github.com/nanocat",
      "followers_url": "https://api.github.com/users/nanocat/followers",
      "following_url": "https://api.github.com/users/nanocat/following{/other_user}",
      "gists_url": "https://api.github.com/users/nanocat/gists{/gist_id}",
      "starred_url": "https://api.github.com/users/nanocat/starred{/owner}{/repo}",
      "subscriptions_url": "https://api.github.com/users/nanocat/subscriptions",
      "organizations_url": "https://api.github.com/users/nanocat/orgs",
      "repos_url": "https://api.github.com/users/nanocat/repos",
      "events_url": "https://api.github.com/users/nanocat/events{/privacy}",
      "received_events_url": "https://api.github.com/users/nanocat/received_events",
      "type": "User",
      "site_admin": false
    }];

    // Mock the TMDB configuration request response
    nock('https://api.github.com')
      .get('/users/octocat/followers')
      .reply(200, followersResponse);
  });

  it('returns users followers', function() {

    var username = 'octocat';

    return getUserFollowers(username).then(function(followers) {
      // It should return an array object
      expect(Array.isArray(followers)).to.equal(true);
      // Ensure that at least one follower is in the array
      expect(followers).to.have.length.above(1);
    });

  });
});


本地做测试时,用完nock做完mock后,在请求nock设定的url,只有用http.get(){ res.on('data') ... res.once('end')},现在on data里接收数据,之后在end里取 on data里接收整理过的数据才好用,用request包或者其他的方式都不好用,不知道为什么,可能是兼容性问题

Nock: https://github.com/node-nock/nock

	var req = http.get(`${host_port}/xxx/xxx?a=a1`, function(res) {
		res.setEncoding('utf8');
		var body = '';
		// console.log("http.get res nock nocknock res: " + JSON.stringify(res));
		res.on('data', function(chunk) {
				body += chunk;
			})
			.once('end', function() {
				// console.log("http.get res nock body once end : " + body);
				expect(JSON.parse(body).status).to.equal(httpStatus.OK);
				scope.done();
				done();
			});
	});


2,如果测试http请求的request/response,就需要对req/res做mock,可以用node-mocks-http,测试过可行。

另一个没有验证的mock框架mock-express:https://www.npmjs.com/package/mock-express

//sample

Workshop - 对Express中间件进行单元测试:http://blog.leapoahead.com/2015/09/09/unittesting-express-middlewares/

tjwudi/unit-testing-express-middlewares-example:https://github.com/tjwudi/unit-testing-express-middlewares-example

//另一个很不错的sample,比较全面

How To Test Your Express Controllers:https://www.terlici.com/2015/09/21/node-express-controller-testing.html

https://github.com/howardabrams/node-mocks-http

——————————————————————————————————————






---------------------------------------------

//先了解一下nodejs的单元测试

Node.js 单元测试:我要写测试: 

http://taobaofed.org/blog/2015/12/10/nodejs-unit-tests/


nodejs单元测试ppt:

http://html5ify.com/unittesting/slides/#/1

---------------------------------------------



Mocha单元测试简介:

http://unitjs.com/guide/mocha.html


Mocha官网和Github:

https://mochajs.org/

NodeJs测试框架Mocha的简单介绍:http://blog.csdn.net/leoleocs/article/details/50016263

https://github.com/mochajs/mocha


测试框架mochajs详解:

http://www.cnblogs.com/Leo_wl/p/5734889.html

测试框架 Mocha 实例教程《作者: 阮一峰》:

http://www.ruanyifeng.com/blog/2015/12/a-mocha-tutorial-of-examples.html

javascript单元测试框架mochajs详解《了解api》

http://www.cnblogs.com/tzyy/p/5729602.html#_h1_56


Nodejs开源项目里怎么样写测试、CI和代码测试覆盖率:

https://cnodejs.org/topic/558df089ebf9c92d17e73358


wx 是一个不错的微信应用框架,接口和网站做的也不错,和wechat-api是类似的项目

群里有人问哪个好

朴灵说:“不写测试的项目都不是好项目”

确实wx目前还没有测试,对于一个开源项目来说,没有测试和代码覆盖率是不完善的,而且从技术选型来说,大多是不敢选的。

那么Nodejs开源项目里怎么样写测试、CI和代码测试覆盖率呢?

测试

目前主流的就bdd和tdd,自己查一下差异

推荐

  • mocha和tape

另外Jasmine也挺有名,angularjs用它,不过挺麻烦的,还有一个选择是qunit,最初是为jquery测试写的,在nodejs里用还是觉得怪怪的。

如果想简单可以tap,它和tape很像,下文会有详细说明

mocha

mocha是tj写的

https://github.com/mochajs/mocha

var assert = require("assert")
describe('truth', function(){
  it('should find the truth', function(){
    assert.equal(1, 1);
  })
})

断言风格,这里默认是assert,推荐使用chaijs这个模块,它提供3种风格

  • Should
  • Expect
  • Assert

rspec里推荐用expect,其实看个人习惯

比较典型一个mocha例子

var assert = require('chai').assert;
var expect = require('chai').expect;
require('chai').should();


describe('Test', function(){
	before(function() {
    // runs before all tests in this block
	
  })
  after(function(){
    // runs after all tests in this block
  })
  beforeEach(function(){
    // runs before each test in this block
  })
  afterEach(function(){
    // runs after each test in this block
  })

  describe('#test()', function(){
    it('should return ok when test finished', function(done){
      assert.equal('sang_test2', 'sang_test2');
      var foo = 'bar';
      expect(foo).to.equal('bar');
      done()
    })
  })
})

说明

  • 理解测试生命周期
  • 理解bdd测试写法

单元测试需要的各个模块说明

  • mocha(Mocha is a feature-rich JavaScript test framework running on node.js and the browser, making asynchronous testing simple and fun.)
  • chai(Chai is a BDD / TDD assertion library for node and the browser that can be delightfully paired with any javascript testing framework.)
  • sinon(Standalone test spies, stubs and mocks for JavaScript.)
  • zombie (页面事件模拟Zombie.js is a lightweight framework for testing client-side JavaScript code in a simulated environment. No browser required.)
  • supertest(接口测试 Super-agent driven library for testing node.js HTTP servers using a fluent API)

更多的看 http://nodeonly.com/2014/11/24/mongoose-test.html

如果你想真正的玩敏捷,从用户故事开始,那么下面这2个库非常必要

啊,黄瓜。。。。

tape:像代码一样跑测试

tape是substack写的测试框架

https://github.com/substack/tape

var test = require('tape').test;
test('equivalence', function(t) {
    t.equal(1, 1, 'these two numbers are equal');
    t.end();
});

tape是非常简单的测试框架,核心价值观是”Tests are code”,所以你可以像代码一样跑测试,

比如

node test/test.js

写个脚本就无比简单了。当然如果你想加’test runner’ 库也有现成的。

The Test Anything Protocol

TAP全称是Test Anything Protocol

它是可靠性测试的一种(tried & true)实现

从1987就有了,有很多语言都实现了。

它说白点就是用贼简单的方式来格式化测试结果,比如

TAP version 13
# equivalence
ok 1 these two numbers are equal

1..1
# tests 1
# pass  1

# ok

比如node里的实现https://github.com/isaacs/node-tap

var tap = require('tap')

// you can test stuff just using the top level object.
// no suites or subtests required.

tap.equal(1, 1, 'check if numbers still work')
tap.notEqual(1, 2, '1 should not equal 2')

// also you can group things into sub-tests.
// Sub-tests will be run in sequential order always,
// so they're great for async things.

tap.test('first stuff', function (t) {
  t.ok(true, 'true is ok')
  t.similar({a: [1,2,3]}, {a: [1,2,3]})
  // call t.end() when you're done
  t.end()
})

一定要区分tap和tape,不要弄混了

科普一下什么是CI

科普一下,CI = Continuous integration 持续集成

Martin Fowler对持续集成是这样定义的:

持续集成是一种软件开发实践,即团队开发成员经常集成他们的工作,通常每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽快地发现集成错误。许多团队发现这个过程可以大大减少集成的问题,让团队能够更快的开发内聚的软件。

它可以

  • 减少风险
  • 减少重复过程
  • 任何时间、任何地点生成可部署的软件
  • 增强项目的可见性
  • 建立团队对开发产品的信心

要素

1.统一的代码库2.自动构建3.自动测试4.每个人每天都要向代码库主干提交代码5.每次代码递交后都会在持续集成服务器上触发一次构建6.保证快速构建7.模拟生产环境的自动测试8.每个人都可以很容易的获取最新可执行的应用程序9.每个人都清楚正在发生的状况10.自动化的部署

也就是说,测试不通过不能部署,只有提交到服务器上,就可以自动跑测试,测试通过后,就可以部署到服务器上了(注意是"staging", 而非"production")。

一般最常的ci软件是jenkins

举个大家熟悉的例子iojs开发中的持续集成就是用的jenkins

https://jenkins-iojs.nodesource.com/

4.png

jenkins是自建环境下用的比较多,如果是开源项目,推荐travis-ci

https://travis-ci.org/

对开源项目做持续集成是免费的(非开源的好贵),所以在github集成的基本是最多的。

对nodejs支持的也非常好。

举2个例子

测试报告

近年随着tdd/bdd,开源项目,和敏捷开发的火热,程序员们不再满足说,我贡献了一个开源项目

要有高要求,我要加测试

要有更高要求,我要把每一个函数都测试到,让别人相信我的代码没有任何问题

上一小节讲的ci,实际上解决了反复测试的自动化问题。但是如何看我的程序里的每一个函数都测试了呢?

答案是测试覆盖率

在nodejs里,推荐istanbul

Istanbul - 官方介绍 a JS code coverage tool written in JS

它可以通过3种途径生成覆盖报告

  • cli
  • 代码
  • gulp插件

安装

$ npm install -g istanbul

执行

$ istanbul cover my-test-script.js -- my test args

它会生成./coverage目录,这里面就是测试报告

比如我的项目里

./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly
    #MongooseDao()
       should return ok when record create
       should return ok when record delete fixture-user
       should return ok when record deleteById
       should return ok when record removeById
       should return ok when record getById
       should return ok when record getAll
       should return ok when record all
       should return ok when record query


  8 passing (50ms)

=============================================================================
Writing coverage object [/Users/sang/workspace/moa/mongoosedao/coverage/coverage.json]
Writing coverage reports at [/Users/sang/workspace/moa/mongoosedao/coverage]
=============================================================================

=============================== Coverage summary ===============================
Statements   : 47.27% ( 26/55 )
Branches     : 8.33% ( 1/12 )
Functions    : 60% ( 9/15 )
Lines        : 47.27% ( 26/55 )
================================================================================

默认,它会生成coverage.json和Icov.info,如果你想生成html也可以的。

比如说,上面的结果47.27%是我测试覆盖的占比,即55个函数,我的测试里只覆盖了26个。

那么我需要有地方能够展示出来啊

实践

我们以mongoosedao项目为例,介绍一下如何集成测试,ci和测试覆盖率

最终效果如图5.png

npm run

package.json里定义自定义执行脚本

"scripts": {
  "start": "npm publish .",
  "test": "./node_modules/.bin/gulp",
  "mocha": "./node_modules/.bin/mocha -u bdd",
  "cov":"./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
},

除了start和test外,都是自定义任务,其他都要加run命令

npm run mocha
npm run cov

更多见npm-run-test教程

gulp watch

var gulp = require('gulp');
var watch = require('gulp-watch');

var path = 'test/**/*.js';

gulp.task('watch', function() {
  gulp.watch(['test/**/*.js', 'lib/*.js'], ['mocha']);
});

var mocha = require('gulp-mocha');

gulp.task('mocha', function () {
    return gulp.src(path , {read: false})
        // gulp-mocha needs filepaths so you can't have any plugins before it 
        .pipe(mocha({reporter: 'spec'}));
});


gulp.task('default',['mocha', 'watch']);

这样就可以执行gulp的时候,当文件变动,会自动触发mocha测试,简化每次都输入npm test这样的操作。

当然你可以玩更多的gulp,如果不熟悉,参考

创建.travis.yml

项目根目录下,和package.json平级

language: node_js
repo_token: COVERALLS.IO_TOKEN
services: mongodb
node_js:
  - "0.12"
  - "0.11"
  - "0.10"
script: npm run mocha
after_script:
  npm run cov

说明

  • 如果依赖mongo等数据库,一定要写services
  • 把测试覆盖率放到执行测试之后,避免报402错误

在travis-ci.org上,github授权,添加repo都比较简单

添加之后,就可以看到,比如

https://travis-ci.org/moajs/mongoosedao

travis-ci实际上根据github的代码变动进行自动持续构建,但是有的时候它不一定更新,或者说,你需要手动选一下:

1.png

点击# 10 passed,这样就可以强制它手动集成了。

其他都很简单,注意替换COVERALLS.IO_TOKEN即可。

创建 .coveralls.yml

https://coveralls.io/是一个代码测试覆盖率的网站,

nodejs下面的代码测试覆盖率,原理是通过istanbul生成测试数据,上传到coveralls网站上,然后以badge的形式展示出来

比如

Coverage Status

具体实践和travis-ci类似,用github账号登陆,然后添加repo,然后在项目根目录下,和package.json平级,增加.coveralls.yml

service_name: travis-pro
repo_token: 99UNur6O7ksBqiwgg1NG1sSFhmu78A0t7

在上,第一次添加repo,显示的是“SET UP COVERALLS”,里面有token,需要放到.coveralls.yml里,

2.png

如果成功提交了,就可以看到数据了

3.png

在readme.md里增加badge

[![Build Status](https://travis-ci.org/moajs/mongoosedao.png?branch=master)](https://travis-ci.org/moajs/mongoosedao)
[![Coverage Status](https://coveralls.io/repos/moajs/mongoosedao/badge.png)](https://coveralls.io/r/moajs/mongoosedao)

它就会显示如下

Build StatusCoverage Status

另外一种用Makefile的玩法实践

举例:https://github.com/node-webot/wechat-api/blob/master/Makefile

TESTS = test/*.js
REPORTER = spec
TIMEOUT = 20000
ISTANBUL = ./node_modules/.bin/istanbul
MOCHA = ./node_modules/mocha/bin/_mocha
COVERALLS = ./node_modules/coveralls/bin/coveralls.js

test:
	@NODE_ENV=test $(MOCHA) -R $(REPORTER) -t $(TIMEOUT) \
		$(MOCHA_OPTS) \
		$(TESTS)

test-cov:
	@$(ISTANBUL) cover --report html $(MOCHA) -- -t $(TIMEOUT) -R spec $(TESTS)

test-coveralls:
	@$(ISTANBUL) cover --report lcovonly $(MOCHA) -- -t $(TIMEOUT) -R spec $(TESTS)
	@echo TRAVIS_JOB_ID $(TRAVIS_JOB_ID)
	@cat ./coverage/lcov.info | $(COVERALLS) && rm -rf ./coverage

test-all: test test-coveralls

.PHONY: test

我个人更喜欢npm+gulp的写法,总是有一种make是c里古老的东东。。。

总结

本文讲了

  • nodejs里常用框架
    • mocha
    • tape
    • tap
    • 前沿技术:cucumber和vowsjs
  • 科普一下CI
  • 测试报告
    • istanbul
  • 实践
    • gulp + npm run
    • mocha
    • travis-ci
    • coveralls
  • 介绍了基于makefile的另一种玩法

全文完




Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐