【问题标题】:Proper Approach to Unit testing a "complex" application service单元测试“复杂”应用程序服务的正确方法
【发布时间】:2010-07-21 21:19:55
【问题描述】:

我有一个应用程序服务(即大型方法)负责协调多个业务对象之间的交互。本质上,它从一个包含客户信息和发票的系统中获取 DTO,然后根据各种业务规则将其翻译并导入到不同的系统中。

public void ProcessQueuedData()
    {
       var queuedItems = _importServiceDAL.LoadQueuedData();

       foreach (var queuedItem in queuedItems)
       {
           var itemType = GetQueuedImportItemType(queuedItem);

           if (itemType == QueuedImportItemType.Invoice)
           {
               var account = _accountDAL.GetAccountByAssocNo(queuedItem.Assoc);
               int agentAccountID;

               if (!account.IsValid)
               {
                   agentAccountId = _accountDAL.AddAccount(queuedItem.Assoc);
               }
               else
               {
                   agentAccountId = account.AgentAccountID;
               }

               /// Do additional processing TBD
           }
       }
    }

对于单元测试,假设服务中的整个过程应该在粒度的基础上逐步测试是否正确,类似于以下内容?

ImportService_ProcessQueuedData_CallsDataAccessLayer_ToLoadQueue

ImportService_ProcessQueuedData_WithQueuedItemToProccess_ChecksIfAccountExists

ImportService_ProcessQueuedData_WithInvoice_CallsDALToCreateAccountIfOneDoesNotExist

这是一个典型的测试:

    [TestMethod()]
    public void ImportService_ProcessQueuedData_WithInvoice_CallsDALToCheckIfAgentAccountExists()
    {
        var accountDAL = MockRepository.GenerateStub<IAccountDAL>();
        var importServiceDAL = MockRepository.GenerateStub<IImportServiceDAL>();

        importServiceDAL.Stub(x => x.LoadQueuedData())
            .Return(GetQueuedImportItemsWithInvoice());

        accountDAL.Stub(x => x.GetAccountByAssocNo("FFFFF"))
            .IgnoreArguments()
            .Return(new Account() { AgentAccountId = 0 });

        var importSvc = new ImportService(accountDAL, importServiceDAL);

        importSvc.ProcessQueuedData();

        accountDAL.AssertWasCalled(a => a.GetAccountByAssocNo("FFFFF"), o => o.IgnoreArguments());
        accountDAL.VerifyAllExpectations();
    }

我的问题是,我最终在每个测试中都进行了如此多的设置,它变得脆弱。这是正确的方法吗?如果是的话,有什么建议可以避免在每个细粒度测试中重复所有这些设置?

【问题讨论】:

    标签: c# unit-testing testing methods


    【解决方案1】:

    我对您的特定应用了解不多,因此无法提出任何具体建议。也就是说,这种面向过程的测试听起来很适合采用Model-based testing 技术。我熟悉用于 Java 的 ModelJUnit 工具(公平披露:我前一段时间曾被聘为该工具的开发人员),但似乎有一个用于 C# 的等效工具,称为 NModel,以及随附的一本书, Model-based Software Testing and Analysis in C#。我这么说的原因是,也许您可​​以随机遍历整个过程,将您的设置代码全部放在一个地方,并允许抽象测试生成为您完成大部分其余的繁重工作。

    【讨论】:

    • 感谢 Gian... 您是否建议 NModel b/c 可能的输入条件的感知数量很大?如果输入类型的组合更加有限,您还会推荐这种方法吗?我试图测试的整个过程实际上只有 6 个条件分支和已知数量的条件在这个 if-then 逻辑中发挥作用。再次感谢您的洞察力。
    • 它更像是一个顺序过程,每个步骤都有可能捕获错误条件,并且可能通过这些操作有多个不同的路径。这就是基于模型的测试真正大放异彩的地方——您只需对模型中的所有状态进行编码,然后让测试系统找出可能的路径。
    【解决方案2】:

    我个人会尝试测试所有代码片段,但不一定将每个部分都作为自己的测试。一项测试检查具有有效帐户的发票是否通过。第二个测试检查具有无效帐户的发票是否创建了一个新帐户。当然,我会模拟 dals,因此不会将数据添加到数据库中。这也允许模拟异常和不应该进行任何操作的情况(队列中没有任何内容,或者可能没有发票。)

    【讨论】:

      【解决方案3】:

      我同意佩德罗的观点。您的第一个示例测试 (ImportService_ProcessQueuedData_CallsDataAccessLayer_ToLoadQueue) 不是必需的,因为其他测试将隐式测试是否调用了 LoadQueuedData - 如果不是,它们将没有数据可操作。您希望对每条路径都进行测试,但不需要对方法中的每一行代码进行单独的测试。如果条件分支有六个路径,则需要六个测试。

      如果我想变得更花哨,我还可以考虑利用对象多态性来减少 if 语句的数量并简化测试。例如,您可以为每个 QueuedImportItemType 设置不同的“处理程序”,并将处理该类型项目的逻辑放在那里,而不是大的 Process 方法。拆分迭代的逻辑以及如何处理每种类型的项目使它们更容易单独测试。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-03-25
        • 1970-01-01
        • 2017-07-25
        • 1970-01-01
        • 2022-01-14
        • 2015-07-24
        • 1970-01-01
        相关资源
        最近更新 更多