【问题标题】:How should you divide a service layer method into testable chunk?您应该如何将服务层方法划分为可测试的块?
【发布时间】:2020-03-12 18:04:12
【问题描述】:
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);
  });
});

或者我应该检查每个被修改对象的状态?你能告诉我如何为上面的方法设置单元测试以及我应该如何划分它以使测试更容易?

【问题讨论】:

    标签: node.js unit-testing tdd


    【解决方案1】:

    您的方法 sendFeedbackServiceProviderMethod 有太多违反 SRP (Single Responsibility Principle) 的责任。您绝对应该考虑使用例如Split phase technique 进行重构。完成后,您可以独立测试这些功能。所有这些findfindByIdupdatesave 功能表示不同的操作,因此可能表示不同的功能。当然,您可以将当前测试保留为验收测试,并在重构这个庞大的功能后创建适当的单元测试。

    您的函数也有很多违反 SLA (Single Level of Abstraction) 的抽象级别。高级概念与低级细节混合在一起,表明要进行重构。

    我建议你将你的 cmets 转换为自解释函数并对它们进行单元测试。

    【讨论】:

    • 你认为我应该如何分割它们,我应该把它们放在哪里?我应该把它们放在服务文件夹中,在同一个文件(feedbackProvider)中吗?这样做的标准方法是什么?
    • 如果函数是常用的,你应该把它们放在一个公共的组件(或文件夹)中,否则你应该把它们放在使用的服务附近。我的划分方法是将这些 cmets 变成函数,一次一个函数和一个单元测试。
    • 所以即使控制器没有调用它们,我也可以将它与其他服务功能放在同一个文件夹中?
    • 真的没有标准的方法,这些决定是关于你如何架构和设计你的产品。有很多方法,不同的团队有不同的对流。我希望我的“帮助”功能靠近我使用它们的地方,以便更好地跟踪。
    • 但是,如果您生成查询数据库(或任何其他存储设备)的函数,您可以引入所有查询发生的存储库层(文件夹)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-08-01
    • 1970-01-01
    • 2020-03-17
    • 2021-04-06
    • 1970-01-01
    • 2022-01-21
    • 1970-01-01
    相关资源
    最近更新 更多