RedisStore.prototype.get = function(sid, fn){
sid = this.prefix + sid;
this.client.get(sid, function(err, data){
try {
if (!data) return fn();
fn(null, JSON.parse(data.toString()));
} catch (err) {
fn(err);
}
});
};
if (buffer) {
row[field.name] += buffer.toString('utf-8');
} else {
row[field.name] = null;
}
Agent.prototype.removeSocket = function(s, name, host, port) {
if (this.sockets[name]) {
var index = this.sockets[name].indexOf(s);
if (index !== -1) {
this.sockets[name].splice(index, 1);
}
} else if (this.sockets[name] && this.sockets[name].length === 0) {
// don't leak
delete this.sockets[name];
delete this.requests[name];
}
if (this.requests[name] && this.requests[name].length) {
// If we have pending requests and a socket gets closed a new one
// needs to be created to take over in the pool for the one that closed.
this.createSocket(name, host, port).emit('free');
}
};
代码质量如何度量? 如果没有测试你如何保证你的代码质量?
单元测试是否也能让产品经理看得懂? 单元测试是否也能成功一个产品需求的Case?
你有足够信心在没有单元测试的情况下发布你的重构代码吗? 如何检测你重构的代码符合需要?
全是绿灯! 单元测试全部跑通!
BDD: behaviour-driven development
我承认,我是@TJ 忠实粉丝... 还有,我喜欢 should 的方式:
Mocha
, should
pass.短址还原: urlrar
├─┬ lib/
│ └── urllib.js
├─┬ test/
│ ├── app.test.js
│ ├── mocha.opts
│ └── urllib.test.js
├─┬ node_modules/
│ ├── mocha/
│ ├── should/
│ └── supertest/
├── app.js
├── index.html
├── Makefile
├── package.json
└── RERAME.md
TESTS = test/*.test.js
REPORTER = spec
TIMEOUT = 10000
JSCOVERAGE = ./node_modules/jscover/bin/jscover
install:
@npm install
test:
@NODE_ENV=test ./node_modules/mocha/bin/mocha \
--reporter $(REPORTER) \
--timeout $(TIMEOUT) \
$(TESTS)
test-cov: lib-cov
@URLRAR_COV=1 $(MAKE) test
@URLRAR_COV=1 $(MAKE) test REPORTER=html-cov > coverage.html
lib-cov:
@rm -rf $@
@$(JSCOVERAGE) lib $@
.PHONY: install test test-cov lib-cov
$ make test
$ make test-cov
--require should
使用 supertest
request(app)
.get('/foo')
.set('x-userid', 'mk2')
.expect('X-Power-By', 'Nodejs')
.expect(200, done);
直接写测试吧:test/app.test.js
var request = require('supertest');
var app = require('../app');
describe('urlrar app', function () {
before(function (done) {
app.listen(0, done);
});
it('GET / should show the title, a form and a text input', function (done) {
request(app)
.get('/')
.expect(200)
.expect('X-Power-By', 'Nodejs')
.end(function (err, res) {
var body = res.text;
// 主页面显示介绍和表单
body.should.include('<title>Shorten URL Expand</title>');
body.should.include('<form');
body.should.include('</form>');
body.should.include('<input');
done(err);
});
});
$ make test
var http = require('http');
var parse = require('url').parse;
var fs = require('fs');
var indexHtml = fs.readFileSync('./index.html');
var app = http.createServer(function (req, res) {
res.setHeader('X-Power-By', 'Nodejs');
res.setHeader('Content-Type', 'text/html');
var info = parse(req.url, true);
if (info.pathname === '/') {
res.end(indexHtml);
}
});
module.exports = app;
$ make test
it('GET /api should have an api', function (done) {
request(app)
.get('/api')
.expect(200)
.expect('X-Power-By', 'Nodejs', done);
});
it('GET /other should not found the page', function (done) {
request(app)
.get('/noexists')
.expect(404)
.expect('Page Not Found!', done);
});
lib/urllib.js
模块来处理使用方式将大致想象为如下:
var urllib = require('./lib/urllib');
urllib.expand(shortenURL, function (err, longURL, redirectCount) {
// go on...
});
var mapping = [
[ 'http://www.baidu.com/', 'http://www.baidu.com/' ],
[ 'http://t.cn/StVkqS', 'http://nodejs.org/community/' ],
[ 'http://url.cn/48JGfK', 'http://baike.baidu.com/view/6341048.htm' ],
[ 'http://t.cn/aK1IFu', 'http://v.youku.com/v_show/id_XMjc2MjY1NjEy.html' ],
// 2 times redirect
[ 'http://url.cn/3OMI3O', 'http://v.youku.com/v_show/id_XMjc2MjY1NjEy.html', 2 ],
[ 'http://luo.bo/17221/', 'http://luo.bo/17221/' ],
[ 'http://t.itc.cn/LLHD6', 'http://app.chrome.csdn.net/work_detail.php?id=57' ],
];
var desc = 'should expand ' + mapping.length + ' shorten urls success';
it(desc, function (done) {
var counter = 0;
mapping.forEach(function (map) {
urllib.expand(map[0], function (err, longurl, redirectCounter) {
should.not.exist(err);
map[1].should.equal(longurl);
if (map[2]) {
redirectCounter.should.equal(map[2]);
}
if (++counter === mapping.length) {
done();
}
})
})
})
exports.expand = function (url, callback) {
var info = parse(url);
var options = {
hostname: info.hostname,
path: info.path,
method: 'HEAD'
};
var request = info.protocol === 'https:' ?
https.request : http.request;
var req = request(options);
if (callback.__redirectCounter === undefined) {
callback.__redirectCounter = 0;
}
req.on('response', function (res) {
if (res.statusCode === 301 || res.statusCode === 302) {
var location = res.headers['location'];
if (++callback.__redirectCounter > exports.maxRedirect) {
return callback(null, location, callback.__redirectCounter);
}
return exports.expand(location, callback);
}
callback(null, url, callback.__redirectCounter);
});
req.end();
};
exports.maxRedirect = 5;
it('should return empty string when shorturl set wrong', function (done) {
urllib.expand('', function (err, longurl) {
should.not.exist(err);
should.not.exist(longurl);
done();
})
});
it('should throw error when pass null', function () {
try {
urllib.expand();
} catch (e) {
e.name.should.equal('TypeError');
e.message.should.equal('undefined is not a function');
}
(function () {
urllib.expand();
}).should.throw();
(function () {
urllib.expand(null);
}).should.throw();
});
describe('expand() server Error', function () {
var app = http.createServer(function (req, res) {
res.destroy();
});
before(function (done) {
app.listen(0, done);
});
it('should return error when server error', function (done) {
var url = 'http://localhost:' + app.address().port + '/foo';
urllib.expand(url, function (err, longurl) {
should.exist(err);
err.should.be.an.instanceof(Error);
err.message.should.equal('connect ECONNREFUSED');
done();
});
});
});
var req = request(options);
req.on('error', function (err) {
callback(err, url, callback.__redirectCounter);
});
req.on('response', function (res) {
// ...
it('GET /api?u=http://t.cn/StVkqS should worked', function (done) {
request(app)
.get('/api?u=http://t.cn/StVkqS')
.expect(200)
.expect('http://nodejs.org/community/', done);
});
var app = http.createServer(function (req, res) {
// ...
if (info.pathname === '/api') {
var query = info.query;
if (!query.u) {
return res.end('`u` argument required.')
}
urllib.expand(query.u, function (err, longurl) {
if (query.cb) {
longurl = query.cb + '(' + JSON.stringify(longurl) + ')';
}
res.end(longurl);
});
return;
}
// ...
});
$ make test
例如上面出现的测试用例,验证的输入参数有多种情况,我们会使用计数器来判断是否全部完成。
it(desc, function (done) {
var counter = 0;
mapping.forEach(function (map) {
urllib.expand(map[0], function (err, longurl, redirectCounter) {
should.not.exist(err);
longurl.should.equal(map[1]);
redirectCounter.should.equal(map[2]);
if (++counter === mapping.length) {
done();
}
})
})
});
it(desc + ' with pedding', function (done) {
done = pedding(mapping.length, done);
mapping.forEach(function (map) {
urllib.expand(map[0], function (err, longurl, redirectCounter) {
should.not.exist(err);
longurl.should.equal(map[1]);
redirectCounter.should.equal(map[2]);
done();
})
})
});
mock mate, easy to mock http
request, fs
access and so on.
fs.readFile
返回错误var mm = require('mm');
var fs = require('fs');
mm.error(fs, 'readFile', 'mock fs.readFile return error');
fs.readFile('/etc/hosts', 'utf8', function (err, content) {
console.log(err); // should return mock err: err.name === 'MockError'
mm.restore(); // remove all mock effects.
fs.readFile('/etc/hosts', 'utf8', function (err, content) {
console.log(err); // should return null
console.log(content); // should show the host list
});
});
var fs = require('fs');
var mm = require('mm');
mm(fs, 'readFile', function (path, callback) {
var content = new Buffer('file contents here');
process.nextTick(callback.bind(null, null, content));
});
/
#