您应该如何将服务层方法划分为可测试的块?
How should you divide a service layer method into testable chunk?
exports.sendFeedbackServiceProviderMethod = function(req, res, next) {
var articleId = req.body.articleId
var commentId = req.body.commentId
var action = req.body.action
var meta = req.body.meta
var Target
var targetId
if (articleId) {
Target = Article
targetId = articleId
}
else if (commentId) {
Target = Comment
targetId = commentId
}
//1. find the target
//Note: will refetch when need to send json, since feedback has been changed
Target.findById(targetId).exec(function(err, target) {
if (err)
return next(err)
if (!target)
return next(helper.getGeneralError('target does not exist'))
//2. find the feedback
var criteria = {}
criteria['statusMeta.createdBy'] = req.user
if (action === 'like' || action === 'dislike' || action === 'unlike' || action === 'undislike')
criteria['type'] = {$in: ['like', 'dislike']}
else if (action === 'share' || action === 'unshare')
criteria['type'] = 'share'
if (articleId)
criteria['target.article'] = articleId
else if (commentId)
criteria['target.comment'] = commentId
Feedback.find(criteria).exec(function(err, feedbacks) {
if (err)
next(err)
if (feedbacks.length === 0) {
//3. Feedback does not exist, create it
var newFeedback = new Feedback()
if (action === 'like' || action === 'dislike' || action === 'share') {
newFeedback.type = action
newFeedback.status = 'normal'
newFeedback.statusMeta.createdBy = req.user
if (articleId)
newFeedback.target.article = targetId
else if (commentId)
newFeedback.target.comment = targetId
if (meta)
newFeedback.meta= meta
}
newFeedback.save(function(err) {
if (err)
return next(err)
//4. save to target feedbacks list
target.feedbacks.push(newFeedback)
target.save(function(err) {
if (err)
return next(err)
//5. save to user feedbacks list
req.user.feedbacks.push(newFeedback)
req.user.save(function(err) {
if (err)
return next(err)
//6. done
//Note: send the target!
//Note: refetch target and populate, since its feedbacks have been changed
var query = Target.findById(targetId)
populateUsersForQuery(query)
populateFeedbacksForQuery(query)
query.exec(function(err, target) {
if (err)
return next(err)
return res.json(target)
})
})
})
})
}
else {
//3x. Found the feedback, update it
var feedback = feedbacks[0] //must be length 1
if (action === 'like' || action === 'dislike' || action === 'share') {
feedback.type = action
feedback.status = 'normal'
feedback.statusMeta.updatedBy = req.user
feedback.statusMeta.updatedDate = new Date
}
else if (action === 'unlike' || action === 'undislike' || action === 'unshare') {
feedback.status = 'deleted'
feedback.statusMeta.deletedBy = req.user
feedback.statusMeta.deletedDate = new Date
}
if (meta)
feedback.meta= meta
feedback.save(function(err) {
if (err)
return next(err)
//4x. done
//Note: send the target!
//Note: refetch target and populate, since its feedbacks have been changed
var query = Target.findById(targetId)
populateUsersForQuery(query)
populateFeedbacksForQuery(query)
query.exec(function(err, target) {
if (err)
return next(err)
return res.json(target)
})
})
}
})
})
}
我有这段代码,它很大,我想知道如何测试它。我应该把它分成更小的部分吗?我应该怎么做,或者我应该保持原样并测试整个块。另外,我不确定测试服务层方法的标准方法是什么,我们是否像这里一样测试整个路由:
describe('POST /user', function() {
it('user.name should be an case-insensitive match for "john"', function(done) {
request(app)
.post('/user')
.send('name=john') // x-www-form-urlencoded upload
.set('Accept', 'application/json')
.expect(function(res) {
res.body.id = 'some fixed id';
res.body.name = res.body.name.toLowerCase();
})
.expect(200, {
id: 'some fixed id',
name: 'john'
}, done);
});
});
或者我应该检查每个修改过的对象的状态?你能告诉我如何为上述方法设置单元测试以及我应该如何划分它以使测试更容易吗?
您的方法 sendFeedbackServiceProviderMethod
有太多责任违反了 SRP (Single Responsibility Principle). You should definitely consider refactoring using for example the Split phase technique。完成后,您可以独立测试功能。所有这些 find
、findById
,update
和 save
功能表示不同的操作,因此可能有不同的功能。您当然可以将当前测试保留为验收测试,并在重构这个巨大的功能后创建适当的单元测试。
您的函数还有很多违反 SLA (Single Level of Abstraction) 的抽象级别。高级概念与低级细节混合在一起,表明需要进行重构。
我建议您将您的评论转换为自解释函数并对其进行单元测试。
exports.sendFeedbackServiceProviderMethod = function(req, res, next) {
var articleId = req.body.articleId
var commentId = req.body.commentId
var action = req.body.action
var meta = req.body.meta
var Target
var targetId
if (articleId) {
Target = Article
targetId = articleId
}
else if (commentId) {
Target = Comment
targetId = commentId
}
//1. find the target
//Note: will refetch when need to send json, since feedback has been changed
Target.findById(targetId).exec(function(err, target) {
if (err)
return next(err)
if (!target)
return next(helper.getGeneralError('target does not exist'))
//2. find the feedback
var criteria = {}
criteria['statusMeta.createdBy'] = req.user
if (action === 'like' || action === 'dislike' || action === 'unlike' || action === 'undislike')
criteria['type'] = {$in: ['like', 'dislike']}
else if (action === 'share' || action === 'unshare')
criteria['type'] = 'share'
if (articleId)
criteria['target.article'] = articleId
else if (commentId)
criteria['target.comment'] = commentId
Feedback.find(criteria).exec(function(err, feedbacks) {
if (err)
next(err)
if (feedbacks.length === 0) {
//3. Feedback does not exist, create it
var newFeedback = new Feedback()
if (action === 'like' || action === 'dislike' || action === 'share') {
newFeedback.type = action
newFeedback.status = 'normal'
newFeedback.statusMeta.createdBy = req.user
if (articleId)
newFeedback.target.article = targetId
else if (commentId)
newFeedback.target.comment = targetId
if (meta)
newFeedback.meta= meta
}
newFeedback.save(function(err) {
if (err)
return next(err)
//4. save to target feedbacks list
target.feedbacks.push(newFeedback)
target.save(function(err) {
if (err)
return next(err)
//5. save to user feedbacks list
req.user.feedbacks.push(newFeedback)
req.user.save(function(err) {
if (err)
return next(err)
//6. done
//Note: send the target!
//Note: refetch target and populate, since its feedbacks have been changed
var query = Target.findById(targetId)
populateUsersForQuery(query)
populateFeedbacksForQuery(query)
query.exec(function(err, target) {
if (err)
return next(err)
return res.json(target)
})
})
})
})
}
else {
//3x. Found the feedback, update it
var feedback = feedbacks[0] //must be length 1
if (action === 'like' || action === 'dislike' || action === 'share') {
feedback.type = action
feedback.status = 'normal'
feedback.statusMeta.updatedBy = req.user
feedback.statusMeta.updatedDate = new Date
}
else if (action === 'unlike' || action === 'undislike' || action === 'unshare') {
feedback.status = 'deleted'
feedback.statusMeta.deletedBy = req.user
feedback.statusMeta.deletedDate = new Date
}
if (meta)
feedback.meta= meta
feedback.save(function(err) {
if (err)
return next(err)
//4x. done
//Note: send the target!
//Note: refetch target and populate, since its feedbacks have been changed
var query = Target.findById(targetId)
populateUsersForQuery(query)
populateFeedbacksForQuery(query)
query.exec(function(err, target) {
if (err)
return next(err)
return res.json(target)
})
})
}
})
})
}
我有这段代码,它很大,我想知道如何测试它。我应该把它分成更小的部分吗?我应该怎么做,或者我应该保持原样并测试整个块。另外,我不确定测试服务层方法的标准方法是什么,我们是否像这里一样测试整个路由:
describe('POST /user', function() {
it('user.name should be an case-insensitive match for "john"', function(done) {
request(app)
.post('/user')
.send('name=john') // x-www-form-urlencoded upload
.set('Accept', 'application/json')
.expect(function(res) {
res.body.id = 'some fixed id';
res.body.name = res.body.name.toLowerCase();
})
.expect(200, {
id: 'some fixed id',
name: 'john'
}, done);
});
});
或者我应该检查每个修改过的对象的状态?你能告诉我如何为上述方法设置单元测试以及我应该如何划分它以使测试更容易吗?
您的方法 sendFeedbackServiceProviderMethod
有太多责任违反了 SRP (Single Responsibility Principle). You should definitely consider refactoring using for example the Split phase technique。完成后,您可以独立测试功能。所有这些 find
、findById
,update
和 save
功能表示不同的操作,因此可能有不同的功能。您当然可以将当前测试保留为验收测试,并在重构这个巨大的功能后创建适当的单元测试。
您的函数还有很多违反 SLA (Single Level of Abstraction) 的抽象级别。高级概念与低级细节混合在一起,表明需要进行重构。
我建议您将您的评论转换为自解释函数并对其进行单元测试。