使用 Bookshelf.js 和 knex.js 进行单元测试
Unit testing with Bookshelf.js and knex.js
我是 Node 的新手,正在使用 knex 和书架开发一个项目。我在对我的代码进行单元测试时遇到了一些麻烦,我不确定自己做错了什么。
基本上我有一个如下所示的模型(称为 VorcuProduct):
var VorcuProduct = bs.Model.extend({
tableName: 'vorcu_products'
});
module.exports.VorcuProduct = VorcuProduct
还有一个函数,如果 VorcuProduct 在数据库中不存在,则保存它。非常简单。执行此操作的函数如下所示:
function subscribeToUpdates(productInformation, callback) {
model.VorcuProduct
.where({product_id: productInformation.product_id, store_id: productInformation.store_id})
.fetch()
.then(function(existing_model) {
if (existing_model == undefined) {
new model.VorcuProduct(productInformation)
.save()
.then(function(new_model) { callback(null, new_model)})
.catch(callback);
} else {
callback(null, existing_model)
}
})
}
在不访问数据库的情况下,哪种测试方法是正确的?我是否需要模拟 fetch
到 return 模型或未定义(取决于测试),然后对 save
做同样的事情?我应该为此使用重新布线吗?
如您所见,我有点迷路了,我们将不胜感激。
谢谢!
我一直在使用 in-memory Sqlite3 databases 进行自动化测试,并取得了巨大成功。我的测试 运行 对 MySQL 需要 10 到 15 分钟,但使用内存中的 sqlite3 数据库只需要 30 秒左右。使用 :memory:
作为连接字符串以利用此技术。
关于单元测试的说明 - 这不是真正的单元测试,因为我们仍在运行对数据库进行查询。这是技术上的集成测试,但是它 运行s 在合理的时间段内,如果你有一个查询密集型应用程序(比如我的)那么这种技术将被证明比单元测试更有效地捕获错误。
Gotchas - Knex/Bookshelf 在应用程序启动时初始化连接,这意味着您保持测试之间的上下文。我建议编写一个模式 create/destroy 脚本,以便您为每个测试构建和销毁表。此外,Sqlite3 对外键约束的敏感度低于 MySQL 或 PostgreSQL,因此请确保您 运行 您的应用程序不时针对其中之一,以确保您的约束正常工作。
这实际上是一个很好的问题,它提出了单元测试的价值和局限性。
在这种特殊情况下,非存根逻辑非常简单——只是一个简单的 if
块,所以这是否值得进行单元测试是有争议的,所以接受的答案是一个很好的答案并指出小规模集成测试的价值。
另一方面,进行单元测试的练习仍然很有价值,因为它指出了代码改进的机会。一般来说,如果测试太复杂,底层代码可能会使用一些重构。在这种情况下,可能会重构 doesProductExist
函数。从 knex/bookshelf 返回承诺而不是转换为回调也将是一个有用的简化。
但为了比较,这里是我对现有代码的真正单元测试的看法:
var rewire = require('rewire');
var sinon = require('sinon');
var expect = require('chai').expect;
var Promise = require('bluebird');
var subscribeToUpdatesModule = rewire('./service/subscribe_to_updates_module');
var subscribeToUpdates = subscribeToUpdatesModule.__get__(subscribeToUpdates);
describe('subscribeToUpdates', function () {
before(function () {
var self = this;
this.sandbox = sinon.sandbox.create();
var VorcuProduct = subscribeToUpdatesModule.__get__('model').VorcuProduct;
this.saveStub = this.sandbox.stub(VorcuProduct.prototype, 'save');
this.saveStub.returns(this.saveResultPromise);
this.fetchStub = this.sandbox.stub()
this.fetchStub.returns(this.fetchResultPromise);
this.sandbox.stub(VorcuProduct, 'where', function () {
return { fetch: self.fetchStub };
})
});
afterEach(function () {
this.sandbox.restore();
});
it('calls save when fetch of existing_model succeeds', function (done) {
var self = this;
this.fetchResultPromise = Promise.resolve('valid result');
this.saveResultPromise = Promise.resolve('save result');
var callback = function (err, result) {
expect(err).to.be.null;
expect(self.saveStub).to.be.called;
expect(result).to.equal('save result');
done();
};
subscribeToUpdates({}, callback);
});
// ... more it(...) blocks
});
我是 Node 的新手,正在使用 knex 和书架开发一个项目。我在对我的代码进行单元测试时遇到了一些麻烦,我不确定自己做错了什么。
基本上我有一个如下所示的模型(称为 VorcuProduct):
var VorcuProduct = bs.Model.extend({
tableName: 'vorcu_products'
});
module.exports.VorcuProduct = VorcuProduct
还有一个函数,如果 VorcuProduct 在数据库中不存在,则保存它。非常简单。执行此操作的函数如下所示:
function subscribeToUpdates(productInformation, callback) {
model.VorcuProduct
.where({product_id: productInformation.product_id, store_id: productInformation.store_id})
.fetch()
.then(function(existing_model) {
if (existing_model == undefined) {
new model.VorcuProduct(productInformation)
.save()
.then(function(new_model) { callback(null, new_model)})
.catch(callback);
} else {
callback(null, existing_model)
}
})
}
在不访问数据库的情况下,哪种测试方法是正确的?我是否需要模拟 fetch
到 return 模型或未定义(取决于测试),然后对 save
做同样的事情?我应该为此使用重新布线吗?
如您所见,我有点迷路了,我们将不胜感激。
谢谢!
我一直在使用 in-memory Sqlite3 databases 进行自动化测试,并取得了巨大成功。我的测试 运行 对 MySQL 需要 10 到 15 分钟,但使用内存中的 sqlite3 数据库只需要 30 秒左右。使用 :memory:
作为连接字符串以利用此技术。
关于单元测试的说明 - 这不是真正的单元测试,因为我们仍在运行对数据库进行查询。这是技术上的集成测试,但是它 运行s 在合理的时间段内,如果你有一个查询密集型应用程序(比如我的)那么这种技术将被证明比单元测试更有效地捕获错误。
Gotchas - Knex/Bookshelf 在应用程序启动时初始化连接,这意味着您保持测试之间的上下文。我建议编写一个模式 create/destroy 脚本,以便您为每个测试构建和销毁表。此外,Sqlite3 对外键约束的敏感度低于 MySQL 或 PostgreSQL,因此请确保您 运行 您的应用程序不时针对其中之一,以确保您的约束正常工作。
这实际上是一个很好的问题,它提出了单元测试的价值和局限性。
在这种特殊情况下,非存根逻辑非常简单——只是一个简单的 if
块,所以这是否值得进行单元测试是有争议的,所以接受的答案是一个很好的答案并指出小规模集成测试的价值。
另一方面,进行单元测试的练习仍然很有价值,因为它指出了代码改进的机会。一般来说,如果测试太复杂,底层代码可能会使用一些重构。在这种情况下,可能会重构 doesProductExist
函数。从 knex/bookshelf 返回承诺而不是转换为回调也将是一个有用的简化。
但为了比较,这里是我对现有代码的真正单元测试的看法:
var rewire = require('rewire');
var sinon = require('sinon');
var expect = require('chai').expect;
var Promise = require('bluebird');
var subscribeToUpdatesModule = rewire('./service/subscribe_to_updates_module');
var subscribeToUpdates = subscribeToUpdatesModule.__get__(subscribeToUpdates);
describe('subscribeToUpdates', function () {
before(function () {
var self = this;
this.sandbox = sinon.sandbox.create();
var VorcuProduct = subscribeToUpdatesModule.__get__('model').VorcuProduct;
this.saveStub = this.sandbox.stub(VorcuProduct.prototype, 'save');
this.saveStub.returns(this.saveResultPromise);
this.fetchStub = this.sandbox.stub()
this.fetchStub.returns(this.fetchResultPromise);
this.sandbox.stub(VorcuProduct, 'where', function () {
return { fetch: self.fetchStub };
})
});
afterEach(function () {
this.sandbox.restore();
});
it('calls save when fetch of existing_model succeeds', function (done) {
var self = this;
this.fetchResultPromise = Promise.resolve('valid result');
this.saveResultPromise = Promise.resolve('save result');
var callback = function (err, result) {
expect(err).to.be.null;
expect(self.saveStub).to.be.called;
expect(result).to.equal('save result');
done();
};
subscribeToUpdates({}, callback);
});
// ... more it(...) blocks
});